@arnilo/prism-coding-agent 0.0.96 → 0.1.1

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +139 -3
  2. package/README.md +48 -19
  3. package/dist/ask-user-decision.d.ts +160 -0
  4. package/dist/ask-user-decision.js +495 -0
  5. package/dist/atomic-write.d.ts +3 -0
  6. package/dist/atomic-write.js +24 -0
  7. package/dist/checks.js +5 -0
  8. package/dist/coding-checkpoint.js +6 -15
  9. package/dist/delete.d.ts +29 -0
  10. package/dist/delete.js +119 -0
  11. package/dist/edit-diff.js +1 -4
  12. package/dist/edit.d.ts +5 -1
  13. package/dist/edit.js +20 -9
  14. package/dist/effects.d.ts +33 -0
  15. package/dist/effects.js +89 -0
  16. package/dist/execution-policy.d.ts +8 -3
  17. package/dist/execution-policy.js +5 -2
  18. package/dist/file-mutation-queue.js +1 -2
  19. package/dist/forge/github.d.ts +2 -0
  20. package/dist/forge/github.js +554 -0
  21. package/dist/forge/index.d.ts +3 -0
  22. package/dist/forge/index.js +3 -0
  23. package/dist/forge/types.d.ts +150 -0
  24. package/dist/forge/types.js +19 -0
  25. package/dist/git-aware-repository.d.ts +25 -0
  26. package/dist/git-aware-repository.js +268 -0
  27. package/dist/git-exec.js +1 -1
  28. package/dist/git-tools.d.ts +4 -1
  29. package/dist/git-tools.js +15 -7
  30. package/dist/git.d.ts +3 -3
  31. package/dist/git.js +14 -14
  32. package/dist/glob-match.d.ts +6 -0
  33. package/dist/glob-match.js +81 -0
  34. package/dist/glob.d.ts +14 -0
  35. package/dist/glob.js +147 -0
  36. package/dist/goal-verify.d.ts +66 -0
  37. package/dist/goal-verify.js +280 -0
  38. package/dist/index.d.ts +63 -30
  39. package/dist/index.js +40 -16
  40. package/dist/language/client.d.ts +44 -0
  41. package/dist/language/client.js +290 -0
  42. package/dist/language/framing.d.ts +23 -0
  43. package/dist/language/framing.js +112 -0
  44. package/dist/language/index.d.ts +4 -0
  45. package/dist/language/index.js +4 -0
  46. package/dist/language/intelligence.d.ts +10 -0
  47. package/dist/language/intelligence.js +526 -0
  48. package/dist/language/types.d.ts +106 -0
  49. package/dist/language/types.js +21 -0
  50. package/dist/lifecycle.d.ts +75 -0
  51. package/dist/lifecycle.js +102 -0
  52. package/dist/limits.d.ts +41 -0
  53. package/dist/limits.js +41 -0
  54. package/dist/list.js +6 -10
  55. package/dist/move.d.ts +24 -0
  56. package/dist/move.js +150 -0
  57. package/dist/mutation-path.d.ts +7 -0
  58. package/dist/mutation-path.js +51 -0
  59. package/dist/output-accumulator.d.ts +8 -0
  60. package/dist/output-accumulator.js +45 -1
  61. package/dist/path-utils.js +1 -1
  62. package/dist/process/index.d.ts +3 -0
  63. package/dist/process/index.js +3 -0
  64. package/dist/process/sessions.d.ts +2 -0
  65. package/dist/process/sessions.js +592 -0
  66. package/dist/process/types.d.ts +146 -0
  67. package/dist/process/types.js +19 -0
  68. package/dist/read-path-set.d.ts +14 -0
  69. package/dist/read-path-set.js +26 -0
  70. package/dist/read.d.ts +3 -0
  71. package/dist/read.js +11 -17
  72. package/dist/repository.d.ts +54 -3
  73. package/dist/repository.js +144 -38
  74. package/dist/search.d.ts +1 -1
  75. package/dist/search.js +91 -27
  76. package/dist/shell.d.ts +3 -0
  77. package/dist/shell.js +23 -8
  78. package/dist/truncate.js +1 -1
  79. package/dist/write.d.ts +5 -1
  80. package/dist/write.js +19 -6
  81. package/package.json +6 -4
@@ -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/limits.d.ts CHANGED
@@ -53,6 +53,9 @@ export declare const DEFAULT_MAX_GIT_MESSAGE_BYTES: number;
53
53
  export declare const HARD_MAX_GIT_MESSAGE_BYTES: number;
54
54
  export declare const DEFAULT_MAX_GIT_OUTPUT_BYTES: number;
55
55
  export declare const HARD_MAX_GIT_OUTPUT_BYTES: number;
56
+ /** Phase 9 freeze: `git ls-files` stdout cap for ignore-aware enumeration. */
57
+ export declare const DEFAULT_MAX_LS_FILES_OUTPUT_BYTES: number;
58
+ export declare const HARD_MAX_LS_FILES_OUTPUT_BYTES: number;
56
59
  export declare const DEFAULT_MAX_GIT_DIFF_LINES = 10000;
57
60
  export declare const HARD_MAX_GIT_DIFF_LINES = 100000;
58
61
  export declare const DEFAULT_MAX_GIT_CHANGED_FILES = 1000;
@@ -92,6 +95,44 @@ export declare const DEFAULT_MAX_CHECK_SUMMARY_BYTES = 1024;
92
95
  export declare const HARD_MAX_CHECK_SUMMARY_BYTES = 8192;
93
96
  export declare const DEFAULT_MAX_CODING_CHECKPOINT_BYTES: number;
94
97
  export declare const HARD_MAX_CODING_CHECKPOINT_BYTES: number;
98
+ /** Phase 9 freeze: language-intelligence / LSP client caps. */
99
+ export declare const DEFAULT_MAX_LSP_MESSAGE_BYTES: number;
100
+ export declare const HARD_MAX_LSP_MESSAGE_BYTES: number;
101
+ export declare const DEFAULT_MAX_LSP_DIAGNOSTICS_PER_FILE = 200;
102
+ export declare const HARD_MAX_LSP_DIAGNOSTICS_PER_FILE = 1000;
103
+ export declare const DEFAULT_MAX_LSP_PENDING_REQUESTS = 32;
104
+ export declare const HARD_MAX_LSP_PENDING_REQUESTS = 128;
105
+ export declare const DEFAULT_MAX_LSP_RESULTS_PER_QUERY = 500;
106
+ export declare const HARD_MAX_LSP_RESULTS_PER_QUERY = 5000;
107
+ export declare const DEFAULT_MAX_LSP_TIMEOUT_MS = 30000;
108
+ export declare const HARD_MAX_LSP_TIMEOUT_MS = 120000;
109
+ export declare const DEFAULT_MAX_LSP_SERVERS = 4;
110
+ export declare const HARD_MAX_LSP_SERVERS = 8;
111
+ /** Freeze: restart budget is fixed at 3 (not host-configurable above). */
112
+ export declare const LSP_RESTARTS_PER_SERVER = 3;
113
+ /** Phase 9 freeze: managed process-session caps. */
114
+ export declare const DEFAULT_MAX_PROCESS_SESSIONS = 8;
115
+ export declare const HARD_MAX_PROCESS_SESSIONS = 32;
116
+ export declare const DEFAULT_MAX_PROCESS_INPUT_BYTES: number;
117
+ export declare const HARD_MAX_PROCESS_INPUT_BYTES: number;
118
+ export declare const DEFAULT_MAX_PROCESS_LIFETIME_MS: number;
119
+ export declare const HARD_MAX_PROCESS_LIFETIME_MS: number;
120
+ export declare const DEFAULT_MAX_PROCESS_OUTPUT_CHUNK_BYTES: number;
121
+ export declare const HARD_MAX_PROCESS_OUTPUT_CHUNK_BYTES: number;
122
+ /** Total output reuses existing accumulator ceilings (64 MiB / 1 GiB). */
123
+ export declare const DEFAULT_MAX_PROCESS_TOTAL_OUTPUT_BYTES: number;
124
+ export declare const HARD_MAX_PROCESS_TOTAL_OUTPUT_BYTES: number;
125
+ /** Forge (GitHub adapter) defaults and hard caps (Phase 9 Task 5). */
126
+ export declare const DEFAULT_MAX_FORGE_PAGES_PER_OPERATION = 10;
127
+ export declare const HARD_MAX_FORGE_PAGES_PER_OPERATION = 100;
128
+ export declare const DEFAULT_MAX_FORGE_PAYLOAD_BYTES: number;
129
+ export declare const HARD_MAX_FORGE_PAYLOAD_BYTES: number;
130
+ export declare const DEFAULT_MAX_FORGE_COMMENTS_PER_REVIEW = 100;
131
+ export declare const HARD_MAX_FORGE_COMMENTS_PER_REVIEW = 1000;
132
+ export declare const DEFAULT_MAX_FORGE_REQUEST_CONCURRENCY = 4;
133
+ export declare const HARD_MAX_FORGE_REQUEST_CONCURRENCY = 8;
134
+ export declare const DEFAULT_MAX_FORGE_REQUEST_TIMEOUT_MS = 30000;
135
+ export declare const HARD_MAX_FORGE_REQUEST_TIMEOUT_MS = 120000;
95
136
  /** Validate one configurable coding resource limit. Invalid values fail instead of clamping. */
96
137
  export declare function validateCodingLimit(name: string, value: number, hardCap: number): number;
97
138
  /** Validate a non-negative integer limit (0 allowed), still capped. */
package/dist/limits.js CHANGED
@@ -53,6 +53,9 @@ export const DEFAULT_MAX_GIT_MESSAGE_BYTES = 64 * 1024;
53
53
  export const HARD_MAX_GIT_MESSAGE_BYTES = 256 * 1024;
54
54
  export const DEFAULT_MAX_GIT_OUTPUT_BYTES = 4 * 1024 * 1024;
55
55
  export const HARD_MAX_GIT_OUTPUT_BYTES = 64 * 1024 * 1024;
56
+ /** Phase 9 freeze: `git ls-files` stdout cap for ignore-aware enumeration. */
57
+ export const DEFAULT_MAX_LS_FILES_OUTPUT_BYTES = 8 * 1024 * 1024;
58
+ export const HARD_MAX_LS_FILES_OUTPUT_BYTES = 64 * 1024 * 1024;
56
59
  export const DEFAULT_MAX_GIT_DIFF_LINES = 10_000;
57
60
  export const HARD_MAX_GIT_DIFF_LINES = 100_000;
58
61
  export const DEFAULT_MAX_GIT_CHANGED_FILES = 1_000;
@@ -92,6 +95,44 @@ export const DEFAULT_MAX_CHECK_SUMMARY_BYTES = 1_024;
92
95
  export const HARD_MAX_CHECK_SUMMARY_BYTES = 8_192;
93
96
  export const DEFAULT_MAX_CODING_CHECKPOINT_BYTES = 64 * 1024;
94
97
  export const HARD_MAX_CODING_CHECKPOINT_BYTES = 512 * 1024;
98
+ /** Phase 9 freeze: language-intelligence / LSP client caps. */
99
+ export const DEFAULT_MAX_LSP_MESSAGE_BYTES = 4 * 1024 * 1024;
100
+ export const HARD_MAX_LSP_MESSAGE_BYTES = 32 * 1024 * 1024;
101
+ export const DEFAULT_MAX_LSP_DIAGNOSTICS_PER_FILE = 200;
102
+ export const HARD_MAX_LSP_DIAGNOSTICS_PER_FILE = 1_000;
103
+ export const DEFAULT_MAX_LSP_PENDING_REQUESTS = 32;
104
+ export const HARD_MAX_LSP_PENDING_REQUESTS = 128;
105
+ export const DEFAULT_MAX_LSP_RESULTS_PER_QUERY = 500;
106
+ export const HARD_MAX_LSP_RESULTS_PER_QUERY = 5_000;
107
+ export const DEFAULT_MAX_LSP_TIMEOUT_MS = 30_000;
108
+ export const HARD_MAX_LSP_TIMEOUT_MS = 120_000;
109
+ export const DEFAULT_MAX_LSP_SERVERS = 4;
110
+ export const HARD_MAX_LSP_SERVERS = 8;
111
+ /** Freeze: restart budget is fixed at 3 (not host-configurable above). */
112
+ export const LSP_RESTARTS_PER_SERVER = 3;
113
+ /** Phase 9 freeze: managed process-session caps. */
114
+ export const DEFAULT_MAX_PROCESS_SESSIONS = 8;
115
+ export const HARD_MAX_PROCESS_SESSIONS = 32;
116
+ export const DEFAULT_MAX_PROCESS_INPUT_BYTES = 64 * 1024;
117
+ export const HARD_MAX_PROCESS_INPUT_BYTES = 1024 * 1024;
118
+ export const DEFAULT_MAX_PROCESS_LIFETIME_MS = 4 * 60 * 60 * 1000;
119
+ export const HARD_MAX_PROCESS_LIFETIME_MS = 24 * 60 * 60 * 1000;
120
+ export const DEFAULT_MAX_PROCESS_OUTPUT_CHUNK_BYTES = 50 * 1024;
121
+ export const HARD_MAX_PROCESS_OUTPUT_CHUNK_BYTES = 1024 * 1024;
122
+ /** Total output reuses existing accumulator ceilings (64 MiB / 1 GiB). */
123
+ export const DEFAULT_MAX_PROCESS_TOTAL_OUTPUT_BYTES = DEFAULT_MAX_TOTAL_OUTPUT_BYTES;
124
+ export const HARD_MAX_PROCESS_TOTAL_OUTPUT_BYTES = HARD_MAX_TOTAL_OUTPUT_BYTES;
125
+ /** Forge (GitHub adapter) defaults and hard caps (Phase 9 Task 5). */
126
+ export const DEFAULT_MAX_FORGE_PAGES_PER_OPERATION = 10;
127
+ export const HARD_MAX_FORGE_PAGES_PER_OPERATION = 100;
128
+ export const DEFAULT_MAX_FORGE_PAYLOAD_BYTES = 1024 * 1024;
129
+ export const HARD_MAX_FORGE_PAYLOAD_BYTES = 8 * 1024 * 1024;
130
+ export const DEFAULT_MAX_FORGE_COMMENTS_PER_REVIEW = 100;
131
+ export const HARD_MAX_FORGE_COMMENTS_PER_REVIEW = 1000;
132
+ export const DEFAULT_MAX_FORGE_REQUEST_CONCURRENCY = 4;
133
+ export const HARD_MAX_FORGE_REQUEST_CONCURRENCY = 8;
134
+ export const DEFAULT_MAX_FORGE_REQUEST_TIMEOUT_MS = 30_000;
135
+ export const HARD_MAX_FORGE_REQUEST_TIMEOUT_MS = 120_000;
95
136
  /** Validate one configurable coding resource limit. Invalid values fail instead of clamping. */
96
137
  export function validateCodingLimit(name, value, hardCap) {
97
138
  if (!Number.isSafeInteger(value) || value < 1 || value > hardCap) {
package/dist/list.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
1
2
  import { enforceExecutionPolicy } from "./execution-policy.js";
2
- import { createLocalRepositoryOperations, resolveRepositoryLimits, RepositoryError, } from "./repository.js";
3
3
  import { HARD_MAX_REPO_DEPTH, HARD_MAX_REPO_RESULTS, validateCodingLimit, validateCodingLimitAllowZero } from "./limits.js";
4
+ import { createLocalRepositoryOperations, RepositoryError, resolveRepositoryLimits, } from "./repository.js";
4
5
  function errorResult(toolCallId, message) {
5
6
  return {
6
7
  toolCallId,
@@ -11,9 +12,7 @@ function errorResult(toolCallId, message) {
11
12
  }
12
13
  function formatListText(result) {
13
14
  if (result.entries.length === 0) {
14
- return result.truncated
15
- ? `[truncated by ${result.truncatedBy ?? "limit"} before any entries]`
16
- : "(no entries)";
15
+ return result.truncated ? `[truncated by ${result.truncatedBy ?? "limit"} before any entries]` : "(no entries)";
17
16
  }
18
17
  const lines = result.entries.map((entry) => {
19
18
  const size = entry.size !== undefined ? `\t${entry.size}` : "";
@@ -35,7 +34,8 @@ export function createRepoListTool(cwd, options) {
35
34
  const ops = options?.operations ?? createLocalRepositoryOperations(limits);
36
35
  return {
37
36
  name: "repo_list",
38
- description: `List repository entries under the workspace with deterministic relative paths. Skips hidden names and excluded basenames (default: ${limits.exclude.join(", ")}) unless overridden. Does not follow symlinks. Results paginate with offset/maxResults (default ${limits.maxResults}). Depth default ${limits.maxDepth}.`,
37
+ effect: CODING_OBSERVATION_EFFECT,
38
+ description: `List repository entries under the workspace with deterministic relative paths. Prefer glob when you already know a filename pattern (*.ts, **/src/**). Prefer repo_search to find text inside files. Skips hidden names and excluded basenames (default: ${limits.exclude.join(", ")}) unless overridden. Does not follow symlinks. Results paginate with offset/maxResults (default ${limits.maxResults}). Depth default ${limits.maxDepth}.`,
39
39
  parameters: {
40
40
  type: "object",
41
41
  properties: {
@@ -131,11 +131,7 @@ export function createRepoListTool(cwd, options) {
131
131
  };
132
132
  }
133
133
  catch (error) {
134
- const message = error instanceof RepositoryError
135
- ? error.message
136
- : error instanceof Error
137
- ? error.message
138
- : String(error);
134
+ const message = error instanceof RepositoryError ? error.message : error instanceof Error ? error.message : String(error);
139
135
  return errorResult(toolCallId, message);
140
136
  }
141
137
  },
package/dist/move.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
2
+ import type { MutationStat } from "./delete.js";
3
+ import type { CodingLifecycleEvent } from "./lifecycle.js";
4
+ export interface MoveOperations {
5
+ lstat: (absolutePath: string, options?: {
6
+ signal?: AbortSignal;
7
+ }) => Promise<MutationStat>;
8
+ rename: (from: string, to: string, options?: {
9
+ signal?: AbortSignal;
10
+ }) => Promise<void>;
11
+ unlink: (absolutePath: string, options?: {
12
+ signal?: AbortSignal;
13
+ }) => Promise<void>;
14
+ access: (absolutePath: string, options?: {
15
+ signal?: AbortSignal;
16
+ }) => Promise<void>;
17
+ }
18
+ export interface MoveToolOptions {
19
+ executionPolicy?: ExecutionPolicy;
20
+ operations?: MoveOperations;
21
+ /** Optional consumer-gated lifecycle listener (file_changed / permission_denied). */
22
+ onEvent?: (event: CodingLifecycleEvent) => void;
23
+ }
24
+ export declare function createMoveTool(cwd: string, options?: MoveToolOptions): ToolDefinition;
package/dist/move.js ADDED
@@ -0,0 +1,150 @@
1
+ import { access, constants } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { lstat, rename, unlink } from "node:fs/promises";
4
+ import { promisify } from "node:util";
5
+ import { CODING_LOCAL_EFFECT } from "./effects.js";
6
+ import { enforceExecutionPolicy } from "./execution-policy.js";
7
+ import { withFileMutationQueue } from "./file-mutation-queue.js";
8
+ import { resolveContainedMutationPath } from "./mutation-path.js";
9
+ const accessAsync = promisify(access);
10
+ const defaultMoveOperations = {
11
+ lstat: async (path) => {
12
+ const st = await lstat(path);
13
+ return {
14
+ isFile: () => st.isFile(),
15
+ isDirectory: () => st.isDirectory(),
16
+ isSymbolicLink: () => st.isSymbolicLink(),
17
+ size: st.size,
18
+ };
19
+ },
20
+ rename: (from, to) => rename(from, to).then(() => { }),
21
+ unlink: (path) => unlink(path).then(() => { }),
22
+ access: (path) => accessAsync(path, constants.F_OK),
23
+ };
24
+ function errorResult(toolCallId, message) {
25
+ return {
26
+ toolCallId,
27
+ name: "move",
28
+ content: [{ type: "text", text: message }],
29
+ error: { message },
30
+ };
31
+ }
32
+ async function withDualFileMutationQueue(pathA, pathB, fn) {
33
+ const [first, second] = pathA <= pathB ? [pathA, pathB] : [pathB, pathA];
34
+ return withFileMutationQueue(first, () => withFileMutationQueue(second, fn));
35
+ }
36
+ export function createMoveTool(cwd, options) {
37
+ const ops = options?.operations ?? defaultMoveOperations;
38
+ return {
39
+ name: "move",
40
+ effect: CODING_LOCAL_EFFECT,
41
+ description: "High-risk: move or rename a file within the workspace. Set overwrite=true to replace an existing destination file only (directories/non-empty dest rejected). Does not create parent directories. No trash — host undo is not automatic.",
42
+ parameters: {
43
+ type: "object",
44
+ properties: {
45
+ from: { type: "string", description: "Source path (relative or absolute)" },
46
+ to: { type: "string", description: "Destination path (relative or absolute)" },
47
+ overwrite: {
48
+ type: "boolean",
49
+ description: "Replace an existing destination file when true (default false).",
50
+ },
51
+ },
52
+ required: ["from", "to"],
53
+ additionalProperties: false,
54
+ },
55
+ async execute(args, context) {
56
+ const toolCallId = context.toolCallId;
57
+ const from = typeof args.from === "string" ? args.from : "";
58
+ const to = typeof args.to === "string" ? args.to : "";
59
+ const overwrite = args.overwrite === true;
60
+ if (from.length === 0)
61
+ return errorResult(toolCallId, "from is required and must be a non-empty string.");
62
+ if (to.length === 0)
63
+ return errorResult(toolCallId, "to is required and must be a non-empty string.");
64
+ try {
65
+ let fromPath;
66
+ try {
67
+ fromPath = await resolveContainedMutationPath(cwd, from);
68
+ }
69
+ catch (error) {
70
+ const err = error;
71
+ if (err.code === "ENOENT")
72
+ return errorResult(toolCallId, `Source does not exist: ${from}`);
73
+ throw error;
74
+ }
75
+ const toPath = await resolveContainedMutationPath(cwd, to, { allowMissing: true });
76
+ const policyCheck = await enforceExecutionPolicy(options?.executionPolicy, {
77
+ kind: "move",
78
+ operation: "move",
79
+ paths: [fromPath, toPath],
80
+ risk: "high",
81
+ metadata: { overwrite, sessionId: context.sessionId, runId: context.runId, signal: context.signal },
82
+ }, toolCallId, "move", (denied) => options?.onEvent?.({ type: "permission_denied", ...denied }));
83
+ if (!policyCheck.allowed)
84
+ return policyCheck.result;
85
+ const allowedFrom = policyCheck.action.paths?.[0] ?? fromPath;
86
+ const allowedTo = policyCheck.action.paths?.[1] ?? toPath;
87
+ return await withDualFileMutationQueue(allowedFrom, allowedTo, async () => {
88
+ if (context.signal?.aborted)
89
+ return errorResult(toolCallId, "Operation aborted");
90
+ let fromStat;
91
+ try {
92
+ fromStat = await ops.lstat(allowedFrom, { signal: context.signal });
93
+ }
94
+ catch (error) {
95
+ const err = error;
96
+ if (err.code === "ENOENT")
97
+ return errorResult(toolCallId, `Source does not exist: ${from}`);
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ return errorResult(toolCallId, message);
100
+ }
101
+ const destParent = dirname(allowedTo);
102
+ try {
103
+ await ops.access(destParent, { signal: context.signal });
104
+ }
105
+ catch {
106
+ return errorResult(toolCallId, `Destination parent directory does not exist: ${destParent}`);
107
+ }
108
+ let destStat;
109
+ try {
110
+ destStat = await ops.lstat(allowedTo, { signal: context.signal });
111
+ }
112
+ catch (error) {
113
+ const err = error;
114
+ if (err.code !== "ENOENT") {
115
+ const message = error instanceof Error ? error.message : String(error);
116
+ return errorResult(toolCallId, message);
117
+ }
118
+ }
119
+ if (destStat) {
120
+ if (!overwrite)
121
+ return errorResult(toolCallId, `Destination already exists: ${to}. Pass overwrite=true to replace.`);
122
+ if (destStat.isDirectory())
123
+ return errorResult(toolCallId, `Destination is a directory: ${to}`);
124
+ await ops.unlink(allowedTo, { signal: context.signal });
125
+ }
126
+ if (context.signal?.aborted)
127
+ return errorResult(toolCallId, "Operation aborted");
128
+ await ops.rename(allowedFrom, allowedTo, { signal: context.signal });
129
+ options?.onEvent?.({ type: "file_changed", path: allowedTo, op: "move", toolCallId });
130
+ return {
131
+ toolCallId,
132
+ name: "move",
133
+ content: [{ type: "text", text: `Successfully moved ${from} to ${allowedTo}` }],
134
+ metadata: {
135
+ from: allowedFrom,
136
+ to: allowedTo,
137
+ bytes: fromStat.isFile() ? fromStat.size : undefined,
138
+ overwrite,
139
+ },
140
+ };
141
+ });
142
+ }
143
+ catch (error) {
144
+ const message = error instanceof Error ? error.message : String(error);
145
+ return errorResult(toolCallId, message);
146
+ }
147
+ },
148
+ };
149
+ }
150
+ //# sourceMappingURL=move.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolve a mutation target under `root`. Symlink paths are kept as the link
3
+ * location (not followed). Non-symlink existing paths are realpath'd.
4
+ */
5
+ export declare function resolveContainedMutationPath(root: string, inputPath: string, options?: {
6
+ allowMissing?: boolean;
7
+ }): Promise<string>;
@@ -0,0 +1,51 @@
1
+ import { lstat, realpath } from "node:fs/promises";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+ import { resolveToCwd } from "./path-utils.js";
4
+ function isPathInsideRoot(root, target) {
5
+ const from = resolve(root);
6
+ const to = resolve(target);
7
+ if (to === from)
8
+ return true;
9
+ const rel = relative(from, to);
10
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
11
+ }
12
+ function isMissingPathError(error) {
13
+ return (typeof error === "object" &&
14
+ error !== null &&
15
+ "code" in error &&
16
+ (error.code === "ENOENT" || error.code === "ENOTDIR"));
17
+ }
18
+ /**
19
+ * Resolve a mutation target under `root`. Symlink paths are kept as the link
20
+ * location (not followed). Non-symlink existing paths are realpath'd.
21
+ */
22
+ export async function resolveContainedMutationPath(root, inputPath, options) {
23
+ const rootResolved = resolve(root);
24
+ let rootReal;
25
+ try {
26
+ rootReal = await realpath(rootResolved);
27
+ }
28
+ catch {
29
+ throw new Error(`workspace root is missing or unreadable: ${rootResolved}`);
30
+ }
31
+ const candidate = resolveToCwd(inputPath, rootReal);
32
+ if (!isPathInsideRoot(rootReal, candidate)) {
33
+ throw new Error(`path escapes workspace root: ${inputPath}`);
34
+ }
35
+ try {
36
+ const st = await lstat(candidate);
37
+ if (st.isSymbolicLink())
38
+ return candidate;
39
+ const real = await realpath(candidate);
40
+ if (!isPathInsideRoot(rootReal, real)) {
41
+ throw new Error(`path resolves outside workspace root: ${inputPath}`);
42
+ }
43
+ return real;
44
+ }
45
+ catch (error) {
46
+ if (options?.allowMissing && isMissingPathError(error))
47
+ return candidate;
48
+ throw error;
49
+ }
50
+ }
51
+ //# sourceMappingURL=mutation-path.js.map
@@ -46,6 +46,14 @@ export declare class OutputAccumulator {
46
46
  cleanupTempFile(): Promise<void>;
47
47
  getLastLineBytes(): number;
48
48
  getTotalRawBytes(): number;
49
+ /**
50
+ * Cursor-paged raw bytes for process sessions.
51
+ * Reads from spill file when present, otherwise from in-memory chunks.
52
+ */
53
+ readRaw(cursor: number, maxBytes: number): {
54
+ readonly data: Buffer;
55
+ readonly nextCursor: number;
56
+ };
49
57
  isOutputLimitExceeded(): boolean;
50
58
  hasStorageError(): boolean;
51
59
  private appendDecodedText;
@@ -1,6 +1,6 @@
1
1
  /** Streaming UTF-8 output retention with bounded memory and spill storage. */
2
2
  import { randomBytes } from "node:crypto";
3
- import { closeSync, openSync, writeSync } from "node:fs";
3
+ import { closeSync, openSync, readSync, writeSync } from "node:fs";
4
4
  import { rm } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
@@ -144,6 +144,50 @@ export class OutputAccumulator {
144
144
  getTotalRawBytes() {
145
145
  return this.totalRawBytes;
146
146
  }
147
+ /**
148
+ * Cursor-paged raw bytes for process sessions.
149
+ * Reads from spill file when present, otherwise from in-memory chunks.
150
+ */
151
+ readRaw(cursor, maxBytes) {
152
+ if (!Number.isSafeInteger(cursor) || cursor < 0) {
153
+ throw new Error("cursor must be a non-negative safe integer");
154
+ }
155
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
156
+ throw new Error("maxBytes must be a positive safe integer");
157
+ }
158
+ const total = this.totalRawBytes;
159
+ if (cursor >= total)
160
+ return { data: Buffer.alloc(0), nextCursor: cursor };
161
+ const length = Math.min(maxBytes, total - cursor);
162
+ if (this.tempFilePath) {
163
+ const fd = openSync(this.tempFilePath, "r");
164
+ try {
165
+ const data = Buffer.alloc(length);
166
+ const n = readSync(fd, data, 0, length, cursor);
167
+ return { data: n === length ? data : data.subarray(0, n), nextCursor: cursor + n };
168
+ }
169
+ finally {
170
+ closeSync(fd);
171
+ }
172
+ }
173
+ const data = Buffer.alloc(length);
174
+ let offset = 0;
175
+ let skipped = 0;
176
+ for (const chunk of this.rawChunks) {
177
+ if (offset >= length)
178
+ break;
179
+ if (skipped + chunk.length <= cursor) {
180
+ skipped += chunk.length;
181
+ continue;
182
+ }
183
+ const start = Math.max(0, cursor - skipped);
184
+ const take = Math.min(chunk.length - start, length - offset);
185
+ chunk.copy(data, offset, start, start + take);
186
+ offset += take;
187
+ skipped += chunk.length;
188
+ }
189
+ return { data: offset === length ? data : data.subarray(0, offset), nextCursor: cursor + offset };
190
+ }
147
191
  isOutputLimitExceeded() {
148
192
  return this.exceeded;
149
193
  }
@@ -98,7 +98,7 @@ export function resolveReadPath(filePath, cwd) {
98
98
  }
99
99
  export async function resolveReadPathAsync(filePath, cwd) {
100
100
  const resolved = resolveToCwd(filePath, cwd);
101
- if ((await pathExists(resolved)))
101
+ if (await pathExists(resolved))
102
102
  return resolved;
103
103
  const amPmVariant = tryMacOSScreenshotPath(resolved);
104
104
  if (amPmVariant !== resolved && (await pathExists(amPmVariant)))
@@ -0,0 +1,3 @@
1
+ export type { CodingProcessEvent, CreateProcessSessionsOptions, ProcessExitResult, ProcessOutputChunk, ProcessSandboxBackend, ProcessSandboxHandle, ProcessSandboxStartRequest, ProcessSession, ProcessSessionLimits, ProcessSessionMetadata, ProcessSessions, ProcessSessionState, ProcessStartRequest, ResolvedProcessSessionLimits, } from "./types.js";
2
+ export { ProcessSessionError, resolveProcessSessionLimits } from "./types.js";
3
+ export { createProcessSessions } from "./sessions.js";
@@ -0,0 +1,3 @@
1
+ export { ProcessSessionError, resolveProcessSessionLimits } from "./types.js";
2
+ export { createProcessSessions } from "./sessions.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ import { type CreateProcessSessionsOptions, type ProcessSessions } from "./types.js";
2
+ export declare function createProcessSessions(options: CreateProcessSessionsOptions): ProcessSessions;