@arnilo/prism-coding-agent 0.0.26 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/delete.d.ts +3 -0
- package/dist/delete.js +3 -1
- package/dist/edit.d.ts +3 -0
- package/dist/edit.js +2 -1
- package/dist/execution-policy.d.ts +7 -1
- package/dist/execution-policy.js +4 -1
- package/dist/git-tools.d.ts +3 -0
- package/dist/git-tools.js +4 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lifecycle.d.ts +75 -0
- package/dist/lifecycle.js +102 -0
- package/dist/move.d.ts +3 -0
- package/dist/move.js +2 -1
- package/dist/write.d.ts +3 -0
- package/dist/write.js +2 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.27] - 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `createCodingLifecycleEmitter(options?)` — consumer-gated coding lifecycle events (Phase 10): ships `process_*` (reuses `CodingProcessEvent`) plus `file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`; synchronous bounded `emit`/`on`; drops unknown/oversized events without breaking producer paths; invalid limits fail closed with `CodingLifecycleError` (`ERR_PRISM_LIFECYCLE_LIMIT`); frozen `DEFAULT_LIFECYCLE_MAX_*` / `HARD_LIFECYCLE_MAX_*` caps.
|
|
7
|
+
- `onEvent` option on `createWriteTool` / `createEditTool` / `createMoveTool` / `createDeleteTool` / `createGitWorktreeTool` — tools emit `file_changed` (write/edit/move/delete) and `worktree_changed` (add/remove) after successful mutation, and `permission_denied` via the shared `enforceExecutionPolicy` deny hook (never raw tool arguments).
|
|
8
|
+
- `enforceExecutionPolicy(..., onDenied?)` — optional callback invoked once per policy denial (additive; existing callers unaffected).
|
|
9
|
+
|
|
3
10
|
## [0.0.26] - 2026-08-06
|
|
4
11
|
|
|
5
12
|
### Added
|
package/dist/delete.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
2
|
+
import type { CodingLifecycleEvent } from "./lifecycle.js";
|
|
2
3
|
export interface MutationStat {
|
|
3
4
|
isFile(): boolean;
|
|
4
5
|
isDirectory(): boolean;
|
|
@@ -22,5 +23,7 @@ export interface DeleteOperations {
|
|
|
22
23
|
export interface DeleteToolOptions {
|
|
23
24
|
executionPolicy?: ExecutionPolicy;
|
|
24
25
|
operations?: DeleteOperations;
|
|
26
|
+
/** Optional consumer-gated lifecycle listener (file_changed / permission_denied). */
|
|
27
|
+
onEvent?: (event: CodingLifecycleEvent) => void;
|
|
25
28
|
}
|
|
26
29
|
export declare function createDeleteTool(cwd: string, options?: DeleteToolOptions): ToolDefinition;
|
package/dist/delete.js
CHANGED
|
@@ -62,7 +62,7 @@ export function createDeleteTool(cwd, options) {
|
|
|
62
62
|
paths: [absolutePath],
|
|
63
63
|
risk: "high",
|
|
64
64
|
metadata: { sessionId: context.sessionId, runId: context.runId, signal: context.signal },
|
|
65
|
-
}, toolCallId, "delete");
|
|
65
|
+
}, toolCallId, "delete", (denied) => options?.onEvent?.({ type: "permission_denied", ...denied }));
|
|
66
66
|
if (!policyCheck.allowed)
|
|
67
67
|
return policyCheck.result;
|
|
68
68
|
const allowedPath = policyCheck.action.paths?.[0] ?? absolutePath;
|
|
@@ -84,6 +84,7 @@ export function createDeleteTool(cwd, options) {
|
|
|
84
84
|
return errorResult(toolCallId, "Operation aborted");
|
|
85
85
|
if (st.isFile() || st.isSymbolicLink()) {
|
|
86
86
|
await ops.unlink(allowedPath, { signal: context.signal });
|
|
87
|
+
options?.onEvent?.({ type: "file_changed", path: allowedPath, op: "delete", toolCallId });
|
|
87
88
|
return {
|
|
88
89
|
toolCallId,
|
|
89
90
|
name: "delete",
|
|
@@ -97,6 +98,7 @@ export function createDeleteTool(cwd, options) {
|
|
|
97
98
|
return errorResult(toolCallId, `Directory is not empty: ${path}. Recursive delete is not supported.`);
|
|
98
99
|
}
|
|
99
100
|
await ops.rmdir(allowedPath, { signal: context.signal });
|
|
101
|
+
options?.onEvent?.({ type: "file_changed", path: allowedPath, op: "delete", toolCallId });
|
|
100
102
|
return {
|
|
101
103
|
toolCallId,
|
|
102
104
|
name: "delete",
|
package/dist/edit.d.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { Buffer } from "node:buffer";
|
|
23
23
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
24
|
+
import type { CodingLifecycleEvent } from "./lifecycle.js";
|
|
24
25
|
import { type ReadBeforeWriteOptions } from "./read-path-set.js";
|
|
25
26
|
export interface Edit {
|
|
26
27
|
oldText: string;
|
|
@@ -71,5 +72,7 @@ export interface EditToolOptions extends ReadBeforeWriteOptions {
|
|
|
71
72
|
maxInputBytes?: number;
|
|
72
73
|
/** Maximum replacements per call (default 100). */
|
|
73
74
|
maxEdits?: number;
|
|
75
|
+
/** Optional consumer-gated lifecycle listener (file_changed / permission_denied). */
|
|
76
|
+
onEvent?: (event: CodingLifecycleEvent) => void;
|
|
74
77
|
}
|
|
75
78
|
export declare function createEditTool(cwd: string, options?: EditToolOptions): ToolDefinition;
|
package/dist/edit.js
CHANGED
|
@@ -149,7 +149,7 @@ export function createEditTool(cwd, options) {
|
|
|
149
149
|
paths: [absolutePath],
|
|
150
150
|
risk: "medium",
|
|
151
151
|
metadata: { editCount: edits.length, sessionId: context.sessionId, runId: context.runId, signal: context.signal },
|
|
152
|
-
}, toolCallId, "edit");
|
|
152
|
+
}, toolCallId, "edit", (denied) => options?.onEvent?.({ type: "permission_denied", ...denied }));
|
|
153
153
|
if (!policyCheck.allowed)
|
|
154
154
|
return policyCheck.result;
|
|
155
155
|
const allowedPath = policyCheck.action.paths?.[0] ?? absolutePath;
|
|
@@ -203,6 +203,7 @@ export function createEditTool(cwd, options) {
|
|
|
203
203
|
return errorResult(toolCallId, "Operation aborted");
|
|
204
204
|
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
205
205
|
await ops.writeFile(allowedPath, finalContent, { signal: context.signal });
|
|
206
|
+
options?.onEvent?.({ type: "file_changed", path: allowedPath, op: "edit", toolCallId });
|
|
206
207
|
const diffResult = generateDiffString(baseContent, newContent);
|
|
207
208
|
const patch = generateUnifiedPatch(prepared.path, baseContent, newContent);
|
|
208
209
|
return {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ExecutionAction, ExecutionPolicy, ToolResult } from "@arnilo/prism";
|
|
2
|
-
export declare function enforceExecutionPolicy(policy: ExecutionPolicy | undefined, action: ExecutionAction, toolCallId: string, toolName: string
|
|
2
|
+
export declare function enforceExecutionPolicy(policy: ExecutionPolicy | undefined, action: ExecutionAction, toolCallId: string, toolName: string,
|
|
3
|
+
/** Called once on policy denial so one site emits lifecycle permission_denied for every tool. */
|
|
4
|
+
onDenied?: (input: {
|
|
5
|
+
readonly toolCallId: string;
|
|
6
|
+
readonly toolName: string;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
}) => void): Promise<{
|
|
3
9
|
allowed: true;
|
|
4
10
|
action: ExecutionAction;
|
|
5
11
|
} | {
|
package/dist/execution-policy.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { assertExecutionAllowed, ExecutionDeniedError } from "@arnilo/prism";
|
|
2
|
-
export async function enforceExecutionPolicy(policy, action, toolCallId, toolName
|
|
2
|
+
export async function enforceExecutionPolicy(policy, action, toolCallId, toolName,
|
|
3
|
+
/** Called once on policy denial so one site emits lifecycle permission_denied for every tool. */
|
|
4
|
+
onDenied) {
|
|
3
5
|
if (!policy)
|
|
4
6
|
return { allowed: true, action };
|
|
5
7
|
try {
|
|
@@ -12,6 +14,7 @@ export async function enforceExecutionPolicy(policy, action, toolCallId, toolNam
|
|
|
12
14
|
: error instanceof Error
|
|
13
15
|
? error.message
|
|
14
16
|
: String(error);
|
|
17
|
+
onDenied?.({ toolCallId, toolName, reason: message });
|
|
15
18
|
return {
|
|
16
19
|
allowed: false,
|
|
17
20
|
result: {
|
package/dist/git-tools.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
8
8
|
import { type CodingCheckToolOptions, type NamedCheckDefinition } from "./checks.js";
|
|
9
|
+
import type { CodingLifecycleEvent } from "./lifecycle.js";
|
|
9
10
|
import { type ArtifactWriter, type CreateGitOperationsOptions, type GitOperations } from "./git.js";
|
|
10
11
|
export interface GitToolsOptions {
|
|
11
12
|
readonly executionPolicy?: ExecutionPolicy;
|
|
@@ -19,6 +20,8 @@ export interface GitToolsOptions {
|
|
|
19
20
|
/** Optional named checks included by `createGitTools` when provided. */
|
|
20
21
|
readonly checks?: Readonly<Record<string, NamedCheckDefinition>>;
|
|
21
22
|
readonly checkOptions?: Omit<CodingCheckToolOptions, "checks" | "executionPolicy">;
|
|
23
|
+
/** Optional consumer-gated lifecycle listener (worktree_changed / permission_denied). */
|
|
24
|
+
readonly onEvent?: (event: CodingLifecycleEvent) => void;
|
|
22
25
|
}
|
|
23
26
|
export declare function createGitStatusTool(cwd: string, options?: GitToolsOptions): ToolDefinition;
|
|
24
27
|
export declare function createGitDiffTool(cwd: string, options?: GitToolsOptions): ToolDefinition;
|
package/dist/git-tools.js
CHANGED
|
@@ -243,7 +243,7 @@ export function createGitWorktreeTool(cwd, options) {
|
|
|
243
243
|
paths: path ? [path] : [cwd],
|
|
244
244
|
risk: action === "list" ? "low" : "high",
|
|
245
245
|
metadata: { branch, force, sessionId: context.sessionId, runId: context.runId },
|
|
246
|
-
}, toolCallId, "git_worktree");
|
|
246
|
+
}, toolCallId, "git_worktree", (denied) => options?.onEvent?.({ type: "permission_denied", ...denied }));
|
|
247
247
|
if (!policy.allowed)
|
|
248
248
|
return policy.result;
|
|
249
249
|
try {
|
|
@@ -254,6 +254,9 @@ export function createGitWorktreeTool(cwd, options) {
|
|
|
254
254
|
force,
|
|
255
255
|
signal: context.signal,
|
|
256
256
|
});
|
|
257
|
+
if (action !== "list") {
|
|
258
|
+
options?.onEvent?.({ type: "worktree_changed", action, path: result.path ?? path ?? "", toolCallId });
|
|
259
|
+
}
|
|
257
260
|
const text = action === "list"
|
|
258
261
|
? result.worktrees.map((w) => `${w.path}\t${w.branch ?? ""}\t${w.head ?? ""}`).join("\n") || "(no worktrees)"
|
|
259
262
|
: `ok action=${action} path=${result.path ?? ""}`;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type { CodingCheckToolOptions, NamedCheckDefinition } from "./checks.js";
|
|
|
5
5
|
export { createCodingCheckTool } from "./checks.js";
|
|
6
6
|
export type { DeleteOperations, DeleteToolOptions, MutationStat } from "./delete.js";
|
|
7
7
|
export { createDeleteTool } from "./delete.js";
|
|
8
|
+
export type { CodingLifecycleEvent, CodingLifecycleLimits, CodingLifecycleEmitter, CreateCodingLifecycleEmitterOptions, FileChangeOp, FileChangedEvent, WorktreeChangedEvent, PermissionDeniedEvent, ConfigurationChangedEvent, ResolvedCodingLifecycleLimits, } from "./lifecycle.js";
|
|
9
|
+
export { createCodingLifecycleEmitter, CodingLifecycleError, resolveCodingLifecycleLimits, } from "./lifecycle.js";
|
|
8
10
|
export type { CodingArtifactKind, CodingArtifactRef, CodingCheckpointLimitOptions, CodingCheckpointMetadata, CodingCheckSummary, CodingFingerprints, CodingHandoffSummary, CodingTaskStatus, CodingTodoItem, ResolvedCodingCheckpointLimits, } from "./coding-checkpoint.js";
|
|
9
11
|
export { assertCodingResumeAllowed, buildCodingCheckpointMetadata, CODING_CHECKPOINT_SCHEMA_VERSION, CODING_STATE_KEY, CodingCheckpointError, codingCheckpointStatePatch, codingPlanPathForTask, createCodingArtifactRef, createCodingPlanMarkdown, fingerprintJson, parseCodingPlanTodos, readCodingCheckpointFromState, readCodingPlanFile, resolveCodingCheckpointLimits, validateCodingCheckpointMetadata, verifyCodingArtifactBytes, writeCodingPlanFile, } from "./coding-checkpoint.js";
|
|
10
12
|
export type { Edit, EditOperations, EditToolDetails, EditToolOptions } from "./edit.js";
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { createDirectoryArtifactWriter, createTempArtifactWriter, sha256Hex } fr
|
|
|
8
8
|
export { ASK_USER_DECISION_RATIONALE_COUNT, ASK_USER_DECISION_SUSPEND_REASON, ASK_USER_DECISION_TOOL_NAME, askUserDecisionResumeSchema, createAskUserDecisionResumeValidator, createAskUserDecisionTool, DEFAULT_MAX_ASK_USER_DECISION_BULLET_BYTES, DEFAULT_MAX_ASK_USER_DECISION_CUSTOM_BYTES, DEFAULT_MAX_ASK_USER_DECISION_LABEL_BYTES, DEFAULT_MAX_ASK_USER_DECISION_OPTIONS, DEFAULT_MAX_ASK_USER_DECISION_QUESTION_BYTES, HARD_MAX_ASK_USER_DECISION_BULLET_BYTES, HARD_MAX_ASK_USER_DECISION_CUSTOM_BYTES, HARD_MAX_ASK_USER_DECISION_LABEL_BYTES, HARD_MAX_ASK_USER_DECISION_OPTIONS, HARD_MAX_ASK_USER_DECISION_QUESTION_BYTES, parseAskUserDecisionArgs, resolveAskUserDecisionAnswer, resolveAskUserDecisionLimits, suspendAskUserDecision, toAskUserDecisionSuspendData, validateAskUserDecisionAgentResume, validateAskUserDecisionResume, } from "./ask-user-decision.js";
|
|
9
9
|
export { createCodingCheckTool } from "./checks.js";
|
|
10
10
|
export { createDeleteTool } from "./delete.js";
|
|
11
|
+
export { createCodingLifecycleEmitter, CodingLifecycleError, resolveCodingLifecycleLimits, } from "./lifecycle.js";
|
|
11
12
|
export { assertCodingResumeAllowed, buildCodingCheckpointMetadata, CODING_CHECKPOINT_SCHEMA_VERSION, CODING_STATE_KEY, CodingCheckpointError, codingCheckpointStatePatch, codingPlanPathForTask, createCodingArtifactRef, createCodingPlanMarkdown, fingerprintJson, parseCodingPlanTodos, readCodingCheckpointFromState, readCodingPlanFile, resolveCodingCheckpointLimits, validateCodingCheckpointMetadata, verifyCodingArtifactBytes, writeCodingPlanFile, } from "./coding-checkpoint.js";
|
|
12
13
|
export { createEditTool } from "./edit.js";
|
|
13
14
|
export { classifyGitApplyEffect, classifyGitBranchEffect, classifyGitWorktreeEffect, CODING_LOCAL_EFFECT, CODING_OBSERVATION_EFFECT, CODING_UNSUPPORTED_EFFECT, reconcileCodingToolEffect, } from "./effects.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { CodingProcessEvent } from "./process/types.js";
|
|
2
|
+
export type FileChangeOp = "write" | "edit" | "delete" | "move";
|
|
3
|
+
export interface FileChangedEvent {
|
|
4
|
+
readonly type: "file_changed";
|
|
5
|
+
/** Workspace-relative or policy-checked absolute path. Never a raw file body. */
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly op: FileChangeOp;
|
|
8
|
+
readonly toolCallId?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WorktreeChangedEvent {
|
|
11
|
+
readonly type: "worktree_changed";
|
|
12
|
+
readonly action: "add" | "remove";
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly toolCallId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PermissionDeniedEvent {
|
|
17
|
+
readonly type: "permission_denied";
|
|
18
|
+
/** Policy decision reason. Never includes raw tool arguments. */
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
readonly toolName: string;
|
|
21
|
+
readonly toolCallId?: string;
|
|
22
|
+
readonly approvalId?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ConfigurationChangedEvent {
|
|
25
|
+
readonly type: "configuration_changed";
|
|
26
|
+
/** Changed config keys only; values never travel in the event. */
|
|
27
|
+
readonly keys: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
export type CodingLifecycleEvent = CodingProcessEvent | FileChangedEvent | WorktreeChangedEvent | PermissionDeniedEvent | ConfigurationChangedEvent;
|
|
30
|
+
export declare const DEFAULT_LIFECYCLE_MAX_EVENT_BYTES = 16384;
|
|
31
|
+
export declare const HARD_LIFECYCLE_MAX_EVENT_BYTES = 65536;
|
|
32
|
+
export declare const DEFAULT_LIFECYCLE_MAX_PATH_BYTES = 4096;
|
|
33
|
+
export declare const HARD_LIFECYCLE_MAX_PATH_BYTES = 16384;
|
|
34
|
+
export declare const DEFAULT_LIFECYCLE_MAX_REASON_BYTES = 1024;
|
|
35
|
+
export declare const HARD_LIFECYCLE_MAX_REASON_BYTES = 16384;
|
|
36
|
+
export declare const DEFAULT_LIFECYCLE_MAX_TOOL_NAME_BYTES = 256;
|
|
37
|
+
export declare const HARD_LIFECYCLE_MAX_TOOL_NAME_BYTES = 4096;
|
|
38
|
+
export declare const DEFAULT_LIFECYCLE_MAX_CONFIG_KEYS = 64;
|
|
39
|
+
export declare const HARD_LIFECYCLE_MAX_CONFIG_KEYS = 256;
|
|
40
|
+
export interface CodingLifecycleLimits {
|
|
41
|
+
readonly maxEventBytes?: number;
|
|
42
|
+
readonly maxPathBytes?: number;
|
|
43
|
+
readonly maxReasonBytes?: number;
|
|
44
|
+
readonly maxToolNameBytes?: number;
|
|
45
|
+
readonly maxConfigKeys?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ResolvedCodingLifecycleLimits {
|
|
48
|
+
readonly maxEventBytes: number;
|
|
49
|
+
readonly maxPathBytes: number;
|
|
50
|
+
readonly maxReasonBytes: number;
|
|
51
|
+
readonly maxToolNameBytes: number;
|
|
52
|
+
readonly maxConfigKeys: number;
|
|
53
|
+
}
|
|
54
|
+
export declare class CodingLifecycleError extends Error {
|
|
55
|
+
readonly code = "ERR_PRISM_LIFECYCLE_LIMIT";
|
|
56
|
+
constructor(message: string);
|
|
57
|
+
}
|
|
58
|
+
export declare function resolveCodingLifecycleLimits(limits?: CodingLifecycleLimits): ResolvedCodingLifecycleLimits;
|
|
59
|
+
export interface CodingLifecycleEmitter {
|
|
60
|
+
/**
|
|
61
|
+
* Delivers the event to every registered listener. Returns true when at least one
|
|
62
|
+
* listener received it. Drops (returns false) without throwing for unknown kinds,
|
|
63
|
+
* oversized events, or when no listener is registered, so producer success paths
|
|
64
|
+
* never break on telemetry.
|
|
65
|
+
*/
|
|
66
|
+
emit(event: CodingLifecycleEvent): boolean;
|
|
67
|
+
/** Registers a listener; the returned function unregisters it. */
|
|
68
|
+
on(listener: (event: CodingLifecycleEvent) => void): () => void;
|
|
69
|
+
}
|
|
70
|
+
export interface CreateCodingLifecycleEmitterOptions {
|
|
71
|
+
readonly limits?: CodingLifecycleLimits;
|
|
72
|
+
/** Convenience initial listener, equivalent to `on`. */
|
|
73
|
+
readonly onEvent?: (event: CodingLifecycleEvent) => void;
|
|
74
|
+
}
|
|
75
|
+
export declare function createCodingLifecycleEmitter(options?: CreateCodingLifecycleEmitterOptions): CodingLifecycleEmitter;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer-gated coding lifecycle events (Phase 10 Task 1 freeze).
|
|
3
|
+
*
|
|
4
|
+
* Ships only event kinds with a consumer in 0.0.27 (ACP mapper and/or host
|
|
5
|
+
* `CodingLifecycleEmitter` callback). Deferred kinds — check_started/finished,
|
|
6
|
+
* task_created/completed, compaction_started/finished, subagent_started/stopped —
|
|
7
|
+
* MUST NOT be added here until a consumer exists
|
|
8
|
+
* (scripts/phase10-freeze-manifest.json lifecycle.deferredEvents).
|
|
9
|
+
*/
|
|
10
|
+
import { validateCodingLimit } from "./limits.js";
|
|
11
|
+
export const DEFAULT_LIFECYCLE_MAX_EVENT_BYTES = 16_384;
|
|
12
|
+
export const HARD_LIFECYCLE_MAX_EVENT_BYTES = 65_536;
|
|
13
|
+
export const DEFAULT_LIFECYCLE_MAX_PATH_BYTES = 4_096;
|
|
14
|
+
export const HARD_LIFECYCLE_MAX_PATH_BYTES = 16_384;
|
|
15
|
+
export const DEFAULT_LIFECYCLE_MAX_REASON_BYTES = 1_024;
|
|
16
|
+
export const HARD_LIFECYCLE_MAX_REASON_BYTES = 16_384;
|
|
17
|
+
export const DEFAULT_LIFECYCLE_MAX_TOOL_NAME_BYTES = 256;
|
|
18
|
+
export const HARD_LIFECYCLE_MAX_TOOL_NAME_BYTES = 4_096;
|
|
19
|
+
export const DEFAULT_LIFECYCLE_MAX_CONFIG_KEYS = 64;
|
|
20
|
+
export const HARD_LIFECYCLE_MAX_CONFIG_KEYS = 256;
|
|
21
|
+
export class CodingLifecycleError extends Error {
|
|
22
|
+
code = "ERR_PRISM_LIFECYCLE_LIMIT";
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "CodingLifecycleError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function validate(name, value, hard) {
|
|
29
|
+
try {
|
|
30
|
+
return validateCodingLimit(name, value, hard);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new CodingLifecycleError(error instanceof Error ? error.message : String(error));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function resolveCodingLifecycleLimits(limits) {
|
|
37
|
+
return {
|
|
38
|
+
maxEventBytes: validate("maxEventBytes", limits?.maxEventBytes ?? DEFAULT_LIFECYCLE_MAX_EVENT_BYTES, HARD_LIFECYCLE_MAX_EVENT_BYTES),
|
|
39
|
+
maxPathBytes: validate("maxPathBytes", limits?.maxPathBytes ?? DEFAULT_LIFECYCLE_MAX_PATH_BYTES, HARD_LIFECYCLE_MAX_PATH_BYTES),
|
|
40
|
+
maxReasonBytes: validate("maxReasonBytes", limits?.maxReasonBytes ?? DEFAULT_LIFECYCLE_MAX_REASON_BYTES, HARD_LIFECYCLE_MAX_REASON_BYTES),
|
|
41
|
+
maxToolNameBytes: validate("maxToolNameBytes", limits?.maxToolNameBytes ?? DEFAULT_LIFECYCLE_MAX_TOOL_NAME_BYTES, HARD_LIFECYCLE_MAX_TOOL_NAME_BYTES),
|
|
42
|
+
maxConfigKeys: validate("maxConfigKeys", limits?.maxConfigKeys ?? DEFAULT_LIFECYCLE_MAX_CONFIG_KEYS, HARD_LIFECYCLE_MAX_CONFIG_KEYS),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Frozen shipped kinds: the six CodingProcessEvent kinds plus the four new kinds. */
|
|
46
|
+
const FROZEN_EVENT_TYPES = new Set([
|
|
47
|
+
"process_started",
|
|
48
|
+
"process_exited",
|
|
49
|
+
"process_killed",
|
|
50
|
+
"process_released",
|
|
51
|
+
"process_expired",
|
|
52
|
+
"process_unknown",
|
|
53
|
+
"file_changed",
|
|
54
|
+
"worktree_changed",
|
|
55
|
+
"permission_denied",
|
|
56
|
+
"configuration_changed",
|
|
57
|
+
]);
|
|
58
|
+
export function createCodingLifecycleEmitter(options = {}) {
|
|
59
|
+
const limits = resolveCodingLifecycleLimits(options.limits);
|
|
60
|
+
const listeners = new Set();
|
|
61
|
+
if (options.onEvent)
|
|
62
|
+
listeners.add(options.onEvent);
|
|
63
|
+
return {
|
|
64
|
+
emit(event) {
|
|
65
|
+
if (!event || typeof event !== "object" || !FROZEN_EVENT_TYPES.has(event.type))
|
|
66
|
+
return false;
|
|
67
|
+
if (Buffer.byteLength(JSON.stringify(event), "utf8") > limits.maxEventBytes)
|
|
68
|
+
return false;
|
|
69
|
+
switch (event.type) {
|
|
70
|
+
case "file_changed":
|
|
71
|
+
case "worktree_changed":
|
|
72
|
+
if (Buffer.byteLength(event.path, "utf8") > limits.maxPathBytes)
|
|
73
|
+
return false;
|
|
74
|
+
break;
|
|
75
|
+
case "permission_denied":
|
|
76
|
+
if (Buffer.byteLength(event.reason, "utf8") > limits.maxReasonBytes)
|
|
77
|
+
return false;
|
|
78
|
+
if (Buffer.byteLength(event.toolName, "utf8") > limits.maxToolNameBytes)
|
|
79
|
+
return false;
|
|
80
|
+
break;
|
|
81
|
+
case "configuration_changed":
|
|
82
|
+
if (event.keys.length > limits.maxConfigKeys)
|
|
83
|
+
return false;
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
if (listeners.size === 0)
|
|
89
|
+
return false;
|
|
90
|
+
for (const listener of [...listeners])
|
|
91
|
+
listener(event);
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
on(listener) {
|
|
95
|
+
listeners.add(listener);
|
|
96
|
+
return () => {
|
|
97
|
+
listeners.delete(listener);
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=lifecycle.js.map
|
package/dist/move.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
2
2
|
import type { MutationStat } from "./delete.js";
|
|
3
|
+
import type { CodingLifecycleEvent } from "./lifecycle.js";
|
|
3
4
|
export interface MoveOperations {
|
|
4
5
|
lstat: (absolutePath: string, options?: {
|
|
5
6
|
signal?: AbortSignal;
|
|
@@ -17,5 +18,7 @@ export interface MoveOperations {
|
|
|
17
18
|
export interface MoveToolOptions {
|
|
18
19
|
executionPolicy?: ExecutionPolicy;
|
|
19
20
|
operations?: MoveOperations;
|
|
21
|
+
/** Optional consumer-gated lifecycle listener (file_changed / permission_denied). */
|
|
22
|
+
onEvent?: (event: CodingLifecycleEvent) => void;
|
|
20
23
|
}
|
|
21
24
|
export declare function createMoveTool(cwd: string, options?: MoveToolOptions): ToolDefinition;
|
package/dist/move.js
CHANGED
|
@@ -79,7 +79,7 @@ export function createMoveTool(cwd, options) {
|
|
|
79
79
|
paths: [fromPath, toPath],
|
|
80
80
|
risk: "high",
|
|
81
81
|
metadata: { overwrite, sessionId: context.sessionId, runId: context.runId, signal: context.signal },
|
|
82
|
-
}, toolCallId, "move");
|
|
82
|
+
}, toolCallId, "move", (denied) => options?.onEvent?.({ type: "permission_denied", ...denied }));
|
|
83
83
|
if (!policyCheck.allowed)
|
|
84
84
|
return policyCheck.result;
|
|
85
85
|
const allowedFrom = policyCheck.action.paths?.[0] ?? fromPath;
|
|
@@ -126,6 +126,7 @@ export function createMoveTool(cwd, options) {
|
|
|
126
126
|
if (context.signal?.aborted)
|
|
127
127
|
return errorResult(toolCallId, "Operation aborted");
|
|
128
128
|
await ops.rename(allowedFrom, allowedTo, { signal: context.signal });
|
|
129
|
+
options?.onEvent?.({ type: "file_changed", path: allowedTo, op: "move", toolCallId });
|
|
129
130
|
return {
|
|
130
131
|
toolCallId,
|
|
131
132
|
name: "move",
|
package/dist/write.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
2
|
+
import type { CodingLifecycleEvent } from "./lifecycle.js";
|
|
2
3
|
import { type ReadBeforeWriteOptions } from "./read-path-set.js";
|
|
3
4
|
/**
|
|
4
5
|
* Pluggable operations for the write tool. Override to delegate file writing to remote systems
|
|
@@ -22,5 +23,7 @@ export interface WriteToolOptions extends ReadBeforeWriteOptions {
|
|
|
22
23
|
operations?: WriteOperations;
|
|
23
24
|
/** Maximum UTF-8 input bytes accepted before any filesystem mutation (default 8 MiB). */
|
|
24
25
|
maxInputBytes?: number;
|
|
26
|
+
/** Optional consumer-gated lifecycle listener (file_changed / permission_denied). */
|
|
27
|
+
onEvent?: (event: CodingLifecycleEvent) => void;
|
|
25
28
|
}
|
|
26
29
|
export declare function createWriteTool(cwd: string, options?: WriteToolOptions): ToolDefinition;
|
package/dist/write.js
CHANGED
|
@@ -84,7 +84,7 @@ export function createWriteTool(cwd, options) {
|
|
|
84
84
|
paths: [absolutePath],
|
|
85
85
|
risk: "medium",
|
|
86
86
|
metadata: { bytes, sessionId: context.sessionId, runId: context.runId, signal: context.signal },
|
|
87
|
-
}, toolCallId, "write");
|
|
87
|
+
}, toolCallId, "write", (denied) => options?.onEvent?.({ type: "permission_denied", ...denied }));
|
|
88
88
|
if (!policyCheck.allowed)
|
|
89
89
|
return policyCheck.result;
|
|
90
90
|
const allowedPath = policyCheck.action.paths?.[0] ?? absolutePath;
|
|
@@ -101,6 +101,7 @@ export function createWriteTool(cwd, options) {
|
|
|
101
101
|
if (context.signal?.aborted)
|
|
102
102
|
return errorResult(toolCallId, "Operation aborted");
|
|
103
103
|
await ops.writeFile(allowedPath, content, { maxBytes: maxInputBytes, signal: context.signal });
|
|
104
|
+
options?.onEvent?.({ type: "file_changed", path: allowedPath, op: "write", toolCallId });
|
|
104
105
|
const lines = countLines(content);
|
|
105
106
|
return {
|
|
106
107
|
toolCallId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnilo/prism-coding-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "Optional coding-agent tools (shell, read, write, edit, repo_list, repo_search, glob, delete, move, opt-in Git/check/ask-user-decision, and durable plan/checkpoint helpers) package for Prism.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"diff": "^9.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@arnilo/prism": "0.0.
|
|
32
|
-
"@arnilo/prism-workflows": "0.0.
|
|
31
|
+
"@arnilo/prism": "0.0.27",
|
|
32
|
+
"@arnilo/prism-workflows": "0.0.27"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@arnilo/prism": "file:../..",
|