feat(FN-3545): add agent permission policy model and persistence
Introduces a multi-agent permission policy model in `@fusion/core` (types, store persistence, API exposure) with corresponding dashboard UI wiring in the agent onboarding modal and task detail modal, plus a distributed task ID overlap retry mechanism for cluster creation, an inline fast mode toggle Fusion-Task-Id: FN-3545
This commit is contained in:
70
packages/core/src/__tests__/agent-permission-policy.test.ts
Normal file
70
packages/core/src/__tests__/agent-permission-policy.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
DEFAULT_AGENT_PERMISSION_POLICY_PRESET_ID,
|
||||
getBuiltInAgentPermissionPolicyPresets,
|
||||
isAgentPermissionPolicyPresetId,
|
||||
normalizeAgentPermissionPolicyFromPreset,
|
||||
resolveEffectiveAgentPermissionPolicy,
|
||||
} from "../agent-permission-policy.js";
|
||||
import { AGENT_PERMISSION_POLICY_ACTION_CATEGORIES } from "../types.js";
|
||||
|
||||
describe("agent-permission-policy", () => {
|
||||
it("returns the canonical built-in preset catalog", () => {
|
||||
const presets = getBuiltInAgentPermissionPolicyPresets();
|
||||
expect(presets.map((preset) => preset.id)).toEqual([
|
||||
"unrestricted",
|
||||
"approval-required",
|
||||
"locked-down",
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes unrestricted preset with all categories allow", () => {
|
||||
const policy = normalizeAgentPermissionPolicyFromPreset("unrestricted");
|
||||
for (const category of AGENT_PERMISSION_POLICY_ACTION_CATEGORIES) {
|
||||
expect(policy.rules[category]).toBe("allow");
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes approval-required preset with all categories require-approval", () => {
|
||||
const policy = normalizeAgentPermissionPolicyFromPreset("approval-required");
|
||||
for (const category of AGENT_PERMISSION_POLICY_ACTION_CATEGORIES) {
|
||||
expect(policy.rules[category]).toBe("require-approval");
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes locked-down preset with all categories block", () => {
|
||||
const policy = normalizeAgentPermissionPolicyFromPreset("locked-down");
|
||||
for (const category of AGENT_PERMISSION_POLICY_ACTION_CATEGORIES) {
|
||||
expect(policy.rules[category]).toBe("block");
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves legacy missing policy to unrestricted default", () => {
|
||||
const effective = resolveEffectiveAgentPermissionPolicy(undefined);
|
||||
expect(effective.presetId).toBe(DEFAULT_AGENT_PERMISSION_POLICY_PRESET_ID);
|
||||
for (const category of AGENT_PERMISSION_POLICY_ACTION_CATEGORIES) {
|
||||
expect(effective.rules[category]).toBe("allow");
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves malformed policy payload to unrestricted default", () => {
|
||||
const effective = resolveEffectiveAgentPermissionPolicy({
|
||||
presetId: "not-a-preset" as never,
|
||||
rules: {
|
||||
"git-write": "block",
|
||||
"file-write-delete": "block",
|
||||
"shell-command": "block",
|
||||
"network-api": "block",
|
||||
"task-agent-management": "block",
|
||||
},
|
||||
});
|
||||
expect(effective.presetId).toBe(DEFAULT_AGENT_PERMISSION_POLICY_PRESET_ID);
|
||||
});
|
||||
|
||||
it("validates known preset IDs", () => {
|
||||
expect(isAgentPermissionPolicyPresetId("unrestricted")).toBe(true);
|
||||
expect(isAgentPermissionPolicyPresetId("approval-required")).toBe(true);
|
||||
expect(isAgentPermissionPolicyPresetId("locked-down")).toBe(true);
|
||||
expect(isAgentPermissionPolicyPresetId("custom")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,7 @@ import { mkdtempSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
AGENT_PERMISSION_POLICY_ACTION_CATEGORIES,
|
||||
CheckoutConflictError,
|
||||
getCanonicalAgentAssetDirectoryName,
|
||||
type AgentCapability,
|
||||
@@ -315,6 +316,28 @@ describe("AgentStore", () => {
|
||||
expect(runtimeConfig.runMissedHeartbeatOnStartup).toBe(true);
|
||||
});
|
||||
|
||||
it("stores default unrestricted permission policy for durable agents", async () => {
|
||||
const agent = await store.createAgent({
|
||||
name: "Policy Default",
|
||||
role: "executor",
|
||||
});
|
||||
|
||||
expect(agent.permissionPolicy?.presetId).toBe("unrestricted");
|
||||
for (const category of AGENT_PERMISSION_POLICY_ACTION_CATEGORIES) {
|
||||
expect(agent.permissionPolicy?.rules[category]).toBe("allow");
|
||||
}
|
||||
});
|
||||
|
||||
it("does not backfill permission policy for ephemeral task workers", async () => {
|
||||
const agent = await store.createAgent({
|
||||
name: "executor-FN-100",
|
||||
role: "executor",
|
||||
metadata: { agentKind: "task-worker", taskWorker: true },
|
||||
});
|
||||
|
||||
expect(agent.permissionPolicy).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves custom metadata", async () => {
|
||||
const agent = await store.createAgent({
|
||||
name: "With Meta",
|
||||
@@ -394,6 +417,18 @@ describe("AgentStore", () => {
|
||||
const result = await store.getAgent("agent-nonexistent");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("resolves legacy durable agents without permissionPolicy to unrestricted", async () => {
|
||||
const created = await store.createAgent({ name: "Legacy Policy", role: "executor" });
|
||||
const testDb = (store as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => unknown } } }).db;
|
||||
testDb.prepare("UPDATE agents SET data = json_remove(data, '$.permissionPolicy') WHERE id = ?").run(created.id);
|
||||
|
||||
const hydrated = await store.getAgent(created.id);
|
||||
expect(hydrated?.permissionPolicy?.presetId).toBe("unrestricted");
|
||||
for (const category of AGENT_PERMISSION_POLICY_ACTION_CATEGORIES) {
|
||||
expect(hydrated?.permissionPolicy?.rules[category]).toBe("allow");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── getAccessState ────────────────────────────────────────────────
|
||||
@@ -935,7 +970,7 @@ describe("AgentStore", () => {
|
||||
expect(revisions[0].after.name).toBe("Renamed");
|
||||
});
|
||||
|
||||
it("records revisions for runtimeConfig, permissions, instructions, soul, and memory changes", async () => {
|
||||
it("records revisions for runtimeConfig, permissions, permissionPolicy, instructions, soul, and memory changes", async () => {
|
||||
const created = await store.createAgent({
|
||||
name: "Configurable",
|
||||
role: "executor",
|
||||
@@ -945,6 +980,13 @@ describe("AgentStore", () => {
|
||||
|
||||
await store.updateAgent(created.id, { runtimeConfig: { heartbeatIntervalMs: 10000 } });
|
||||
await store.updateAgent(created.id, { permissions: { canReview: true, canExecute: true } });
|
||||
await store.updateAgent(created.id, { permissionPolicy: { presetId: "locked-down", rules: {
|
||||
"git-write": "block",
|
||||
"file-write-delete": "block",
|
||||
"shell-command": "block",
|
||||
"network-api": "block",
|
||||
"task-agent-management": "block",
|
||||
} } });
|
||||
await store.updateAgent(created.id, { instructionsPath: "docs/agent.md" });
|
||||
await store.updateAgent(created.id, { instructionsText: "Follow safety checks." });
|
||||
await store.updateAgent(created.id, { soul: "Thoughtful collaborator" });
|
||||
@@ -955,6 +997,7 @@ describe("AgentStore", () => {
|
||||
|
||||
expect(changedFields).toContain("runtimeConfig");
|
||||
expect(changedFields).toContain("permissions");
|
||||
expect(changedFields).toContain("permissionPolicy");
|
||||
expect(changedFields).toContain("instructionsPath");
|
||||
expect(changedFields).toContain("instructionsText");
|
||||
expect(changedFields).toContain("soul");
|
||||
|
||||
80
packages/core/src/agent-permission-policy.ts
Normal file
80
packages/core/src/agent-permission-policy.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type {
|
||||
AgentPermissionPolicy,
|
||||
AgentPermissionPolicyPresetId,
|
||||
AgentPermissionPolicyRules,
|
||||
} from "./types.js";
|
||||
import { AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_PRESET_IDS } from "./types.js";
|
||||
|
||||
export const DEFAULT_AGENT_PERMISSION_POLICY_PRESET_ID: AgentPermissionPolicyPresetId = "unrestricted";
|
||||
|
||||
export interface BuiltInAgentPermissionPolicyPreset {
|
||||
id: AgentPermissionPolicyPresetId;
|
||||
name: string;
|
||||
description: string;
|
||||
rules: AgentPermissionPolicyRules;
|
||||
}
|
||||
|
||||
const BUILT_IN_PRESETS: Record<AgentPermissionPolicyPresetId, BuiltInAgentPermissionPolicyPreset> = {
|
||||
unrestricted: {
|
||||
id: "unrestricted",
|
||||
name: "Unrestricted",
|
||||
description: "Allows all runtime action categories (legacy-compatible default).",
|
||||
rules: buildRules("allow"),
|
||||
},
|
||||
"approval-required": {
|
||||
id: "approval-required",
|
||||
name: "Approval Required",
|
||||
description: "Requires approval for all runtime action categories.",
|
||||
rules: buildRules("require-approval"),
|
||||
},
|
||||
"locked-down": {
|
||||
id: "locked-down",
|
||||
name: "Locked Down",
|
||||
description: "Blocks all runtime action categories.",
|
||||
rules: buildRules("block"),
|
||||
},
|
||||
};
|
||||
|
||||
function buildRules(disposition: AgentPermissionPolicyRules[(typeof AGENT_PERMISSION_POLICY_ACTION_CATEGORIES)[number]]): AgentPermissionPolicyRules {
|
||||
return AGENT_PERMISSION_POLICY_ACTION_CATEGORIES.reduce((acc, category) => {
|
||||
acc[category] = disposition;
|
||||
return acc;
|
||||
}, {} as AgentPermissionPolicyRules);
|
||||
}
|
||||
|
||||
export function isAgentPermissionPolicyPresetId(value: unknown): value is AgentPermissionPolicyPresetId {
|
||||
return typeof value === "string" && (AGENT_PERMISSION_POLICY_PRESET_IDS as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
export function getBuiltInAgentPermissionPolicyPresets(): BuiltInAgentPermissionPolicyPreset[] {
|
||||
return AGENT_PERMISSION_POLICY_PRESET_IDS.map((id) => resolveAgentPermissionPolicyPreset(id));
|
||||
}
|
||||
|
||||
export function resolveAgentPermissionPolicyPreset(
|
||||
presetId: AgentPermissionPolicyPresetId,
|
||||
): BuiltInAgentPermissionPolicyPreset {
|
||||
const preset = BUILT_IN_PRESETS[presetId];
|
||||
return {
|
||||
...preset,
|
||||
rules: { ...preset.rules },
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeAgentPermissionPolicyFromPreset(
|
||||
presetId: AgentPermissionPolicyPresetId,
|
||||
): AgentPermissionPolicy {
|
||||
const preset = resolveAgentPermissionPolicyPreset(presetId);
|
||||
return {
|
||||
presetId: preset.id,
|
||||
rules: { ...preset.rules },
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveEffectiveAgentPermissionPolicy(
|
||||
policy: AgentPermissionPolicy | undefined,
|
||||
): AgentPermissionPolicy {
|
||||
if (!policy || !isAgentPermissionPolicyPresetId(policy.presetId)) {
|
||||
return normalizeAgentPermissionPolicyFromPreset(DEFAULT_AGENT_PERMISSION_POLICY_PRESET_ID);
|
||||
}
|
||||
return normalizeAgentPermissionPolicyFromPreset(policy.presetId);
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
AgentConfigRevision,
|
||||
AgentConfigSnapshot,
|
||||
AgentAccessState,
|
||||
AgentPermissionPolicy,
|
||||
OrgTreeNode,
|
||||
InstructionsBundleConfig,
|
||||
AgentRating,
|
||||
@@ -54,6 +55,7 @@ import {
|
||||
import type { RunMutationContext } from "./types.js";
|
||||
import type { TaskStore } from "./store.js";
|
||||
import { computeAccessState } from "./agent-permissions.js";
|
||||
import { resolveEffectiveAgentPermissionPolicy } from "./agent-permission-policy.js";
|
||||
import { Database } from "./db.js";
|
||||
import { createAgentRunSnapshot, createAgentSnapshot, validateSnapshotEnvelope, type AgentRunSnapshot, type AgentSnapshot } from "./shared-mesh-state.js";
|
||||
|
||||
@@ -125,6 +127,7 @@ interface AgentData {
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
pauseReason?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
permissionPolicy?: AgentPermissionPolicy;
|
||||
totalInputTokens?: number;
|
||||
totalOutputTokens?: number;
|
||||
lastError?: string;
|
||||
@@ -606,6 +609,10 @@ export class AgentStore extends EventEmitter {
|
||||
const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath
|
||||
?? (ephemeral ? undefined : getDefaultHeartbeatProcedurePath(agentId, input.name));
|
||||
|
||||
const normalizedPermissionPolicy = ephemeral
|
||||
? input.permissionPolicy
|
||||
: resolveEffectiveAgentPermissionPolicy(input.permissionPolicy);
|
||||
|
||||
const agent: Agent = {
|
||||
id: agentId,
|
||||
name: normalizedName,
|
||||
@@ -620,6 +627,7 @@ export class AgentStore extends EventEmitter {
|
||||
...(input.reportsTo && { reportsTo: input.reportsTo }),
|
||||
...(runtimeConfig && { runtimeConfig }),
|
||||
...(input.permissions && { permissions: input.permissions }),
|
||||
...(normalizedPermissionPolicy && { permissionPolicy: normalizedPermissionPolicy }),
|
||||
...(input.instructionsPath && { instructionsPath: input.instructionsPath }),
|
||||
...(input.instructionsText && { instructionsText: input.instructionsText }),
|
||||
...(input.soul && { soul: input.soul }),
|
||||
@@ -1072,6 +1080,7 @@ export class AgentStore extends EventEmitter {
|
||||
...("runtimeConfig" in updates && { runtimeConfig: updates.runtimeConfig }),
|
||||
...("pauseReason" in updates && { pauseReason: updates.pauseReason }),
|
||||
...("permissions" in updates && { permissions: updates.permissions }),
|
||||
...("permissionPolicy" in updates && { permissionPolicy: updates.permissionPolicy }),
|
||||
...("lastError" in updates && { lastError: updates.lastError }),
|
||||
...("totalInputTokens" in updates && { totalInputTokens: updates.totalInputTokens }),
|
||||
...("totalOutputTokens" in updates && { totalOutputTokens: updates.totalOutputTokens }),
|
||||
@@ -2274,6 +2283,7 @@ export class AgentStore extends EventEmitter {
|
||||
| "reportsTo"
|
||||
| "runtimeConfig"
|
||||
| "permissions"
|
||||
| "permissionPolicy"
|
||||
| "instructionsPath"
|
||||
| "instructionsText"
|
||||
| "soul"
|
||||
@@ -2291,6 +2301,12 @@ export class AgentStore extends EventEmitter {
|
||||
reportsTo: snapshot.reportsTo,
|
||||
runtimeConfig: snapshot.runtimeConfig ? { ...snapshot.runtimeConfig } : undefined,
|
||||
permissions: snapshot.permissions ? { ...snapshot.permissions } : undefined,
|
||||
permissionPolicy: snapshot.permissionPolicy
|
||||
? {
|
||||
presetId: snapshot.permissionPolicy.presetId,
|
||||
rules: { ...snapshot.permissionPolicy.rules },
|
||||
}
|
||||
: undefined,
|
||||
instructionsPath: snapshot.instructionsPath,
|
||||
instructionsText: snapshot.instructionsText,
|
||||
soul: snapshot.soul,
|
||||
@@ -2575,6 +2591,9 @@ export class AgentStore extends EventEmitter {
|
||||
runtimeConfig: data.runtimeConfig,
|
||||
pauseReason: data.pauseReason,
|
||||
permissions: data.permissions,
|
||||
permissionPolicy: isEphemeralAgent(data)
|
||||
? data.permissionPolicy
|
||||
: resolveEffectiveAgentPermissionPolicy(data.permissionPolicy),
|
||||
totalInputTokens: data.totalInputTokens,
|
||||
totalOutputTokens: data.totalOutputTokens,
|
||||
lastError: data.lastError,
|
||||
@@ -2605,6 +2624,7 @@ export class AgentStore extends EventEmitter {
|
||||
runtimeConfig: agent.runtimeConfig,
|
||||
pauseReason: agent.pauseReason,
|
||||
permissions: agent.permissions,
|
||||
permissionPolicy: agent.permissionPolicy,
|
||||
totalInputTokens: agent.totalInputTokens,
|
||||
totalOutputTokens: agent.totalOutputTokens,
|
||||
lastError: agent.lastError,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, buildResearchDocumentKey, SHARED_STATE_SNAPSHOT_VERSION } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter } from "./types.js";
|
||||
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_PRESET_IDS, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, buildResearchDocumentKey, SHARED_STATE_SNAPSHOT_VERSION } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyActionCategory, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter } from "./types.js";
|
||||
export { AGENT_VALID_TRANSITIONS } from "./types.js";
|
||||
export * from "./mesh-replication-protocol.js";
|
||||
export * from "./mesh-task-replication.js";
|
||||
@@ -41,6 +41,15 @@ export {
|
||||
computeAccessState,
|
||||
isValidPermission,
|
||||
} from "./agent-permissions.js";
|
||||
export {
|
||||
DEFAULT_AGENT_PERMISSION_POLICY_PRESET_ID,
|
||||
getBuiltInAgentPermissionPolicyPresets,
|
||||
resolveAgentPermissionPolicyPreset,
|
||||
normalizeAgentPermissionPolicyFromPreset,
|
||||
resolveEffectiveAgentPermissionPolicy,
|
||||
isAgentPermissionPolicyPresetId,
|
||||
} from "./agent-permission-policy.js";
|
||||
export type { BuiltInAgentPermissionPolicyPreset } from "./agent-permission-policy.js";
|
||||
export { AgentStore, DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS } from "./agent-store.js";
|
||||
export type { AgentStoreEvents } from "./agent-store.js";
|
||||
export { ReflectionStore } from "./reflection-store.js";
|
||||
|
||||
@@ -3661,6 +3661,39 @@ export const AGENT_PERMISSIONS = [
|
||||
/** A single canonical permission string. */
|
||||
export type AgentPermission = (typeof AGENT_PERMISSIONS)[number];
|
||||
|
||||
/** Runtime action categories governed by agent permission policy presets. */
|
||||
export const AGENT_PERMISSION_POLICY_ACTION_CATEGORIES = [
|
||||
"git-write",
|
||||
"file-write-delete",
|
||||
"shell-command",
|
||||
"network-api",
|
||||
"task-agent-management",
|
||||
] as const;
|
||||
|
||||
/** A single runtime action category governed by permission policy. */
|
||||
export type AgentPermissionPolicyActionCategory = (typeof AGENT_PERMISSION_POLICY_ACTION_CATEGORIES)[number];
|
||||
|
||||
/** How a runtime action category is handled by permission policy. */
|
||||
export type AgentPermissionPolicyDisposition = "allow" | "block" | "require-approval";
|
||||
|
||||
/** Built-in permission policy preset identifiers for permanent agents. */
|
||||
export const AGENT_PERMISSION_POLICY_PRESET_IDS = ["unrestricted", "approval-required", "locked-down"] as const;
|
||||
|
||||
/** A single built-in permission policy preset identifier. */
|
||||
export type AgentPermissionPolicyPresetId = (typeof AGENT_PERMISSION_POLICY_PRESET_IDS)[number];
|
||||
|
||||
/** Canonical category->disposition map for a permission policy. */
|
||||
export type AgentPermissionPolicyRules = Record<
|
||||
AgentPermissionPolicyActionCategory,
|
||||
AgentPermissionPolicyDisposition
|
||||
>;
|
||||
|
||||
/** First-class persisted permission policy contract for permanent agents. */
|
||||
export interface AgentPermissionPolicy {
|
||||
presetId: AgentPermissionPolicyPresetId;
|
||||
rules: AgentPermissionPolicyRules;
|
||||
}
|
||||
|
||||
/** Describes how an agent's task assignment capability was determined. */
|
||||
export type TaskAssignSource =
|
||||
| "role_default" // Granted automatically by role (e.g., scheduler gets tasks:assign)
|
||||
@@ -3731,6 +3764,8 @@ export interface Agent {
|
||||
pauseReason?: string;
|
||||
/** Capability permission flags */
|
||||
permissions?: Record<string, boolean>;
|
||||
/** Runtime action gating policy (preset + normalized category rules). */
|
||||
permissionPolicy?: AgentPermissionPolicy;
|
||||
/** Cumulative input tokens across all runs */
|
||||
totalInputTokens?: number;
|
||||
/** Cumulative output tokens across all runs */
|
||||
@@ -3873,6 +3908,7 @@ export interface AgentCreateInput {
|
||||
reportsTo?: string;
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
permissions?: Record<string, boolean>;
|
||||
permissionPolicy?: AgentPermissionPolicy;
|
||||
instructionsPath?: string;
|
||||
instructionsText?: string;
|
||||
soul?: string;
|
||||
@@ -3893,6 +3929,7 @@ export interface AgentUpdateInput {
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
pauseReason?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
permissionPolicy?: AgentPermissionPolicy;
|
||||
lastError?: string;
|
||||
totalInputTokens?: number;
|
||||
totalOutputTokens?: number;
|
||||
@@ -3990,6 +4027,7 @@ export interface AgentConfigSnapshot {
|
||||
reportsTo?: string;
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
permissions?: Record<string, boolean>;
|
||||
permissionPolicy?: AgentPermissionPolicy;
|
||||
instructionsPath?: string;
|
||||
instructionsText?: string;
|
||||
soul?: string;
|
||||
@@ -4119,6 +4157,12 @@ export function agentToConfigSnapshot(agent: Agent): AgentConfigSnapshot {
|
||||
reportsTo: agent.reportsTo,
|
||||
runtimeConfig: agent.runtimeConfig ? { ...agent.runtimeConfig } : undefined,
|
||||
permissions: agent.permissions ? { ...agent.permissions } : undefined,
|
||||
permissionPolicy: agent.permissionPolicy
|
||||
? {
|
||||
presetId: agent.permissionPolicy.presetId,
|
||||
rules: { ...agent.permissionPolicy.rules },
|
||||
}
|
||||
: undefined,
|
||||
instructionsPath: agent.instructionsPath,
|
||||
instructionsText: agent.instructionsText,
|
||||
soul: agent.soul,
|
||||
@@ -4148,6 +4192,7 @@ export function diffConfigSnapshots(
|
||||
"reportsTo",
|
||||
"runtimeConfig",
|
||||
"permissions",
|
||||
"permissionPolicy",
|
||||
"instructionsPath",
|
||||
"instructionsText",
|
||||
"soul",
|
||||
|
||||
Reference in New Issue
Block a user