@orc-brain/core 1.0.0

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/dist/autoLoop.d.ts +70 -0
  3. package/dist/autoLoop.d.ts.map +1 -0
  4. package/dist/autoLoop.js +178 -0
  5. package/dist/autoLoop.js.map +1 -0
  6. package/dist/backpressure.d.ts +38 -0
  7. package/dist/backpressure.d.ts.map +1 -0
  8. package/dist/backpressure.js +103 -0
  9. package/dist/backpressure.js.map +1 -0
  10. package/dist/budgetTracker.d.ts +53 -0
  11. package/dist/budgetTracker.d.ts.map +1 -0
  12. package/dist/budgetTracker.js +93 -0
  13. package/dist/budgetTracker.js.map +1 -0
  14. package/dist/config.d.ts +9 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +107 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/escalation.d.ts +43 -0
  19. package/dist/escalation.d.ts.map +1 -0
  20. package/dist/escalation.js +81 -0
  21. package/dist/escalation.js.map +1 -0
  22. package/dist/eventBus.d.ts +30 -0
  23. package/dist/eventBus.d.ts.map +1 -0
  24. package/dist/eventBus.js +42 -0
  25. package/dist/eventBus.js.map +1 -0
  26. package/dist/goalJudge.d.ts +92 -0
  27. package/dist/goalJudge.d.ts.map +1 -0
  28. package/dist/goalJudge.js +254 -0
  29. package/dist/goalJudge.js.map +1 -0
  30. package/dist/index.d.ts +30 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +30 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/modelRouter.d.ts +40 -0
  35. package/dist/modelRouter.d.ts.map +1 -0
  36. package/dist/modelRouter.js +114 -0
  37. package/dist/modelRouter.js.map +1 -0
  38. package/dist/orchestrator.d.ts +242 -0
  39. package/dist/orchestrator.d.ts.map +1 -0
  40. package/dist/orchestrator.js +975 -0
  41. package/dist/orchestrator.js.map +1 -0
  42. package/dist/pacing.d.ts +34 -0
  43. package/dist/pacing.d.ts.map +1 -0
  44. package/dist/pacing.js +49 -0
  45. package/dist/pacing.js.map +1 -0
  46. package/dist/planValidation.d.ts +24 -0
  47. package/dist/planValidation.d.ts.map +1 -0
  48. package/dist/planValidation.js +134 -0
  49. package/dist/planValidation.js.map +1 -0
  50. package/dist/planner.d.ts +70 -0
  51. package/dist/planner.d.ts.map +1 -0
  52. package/dist/planner.js +210 -0
  53. package/dist/planner.js.map +1 -0
  54. package/dist/plugins/host.d.ts +39 -0
  55. package/dist/plugins/host.d.ts.map +1 -0
  56. package/dist/plugins/host.js +109 -0
  57. package/dist/plugins/host.js.map +1 -0
  58. package/dist/plugins/registry.d.ts +71 -0
  59. package/dist/plugins/registry.d.ts.map +1 -0
  60. package/dist/plugins/registry.js +269 -0
  61. package/dist/plugins/registry.js.map +1 -0
  62. package/dist/plugins/secrets.d.ts +30 -0
  63. package/dist/plugins/secrets.d.ts.map +1 -0
  64. package/dist/plugins/secrets.js +99 -0
  65. package/dist/plugins/secrets.js.map +1 -0
  66. package/dist/preflight.d.ts +38 -0
  67. package/dist/preflight.d.ts.map +1 -0
  68. package/dist/preflight.js +108 -0
  69. package/dist/preflight.js.map +1 -0
  70. package/dist/reporting.d.ts +41 -0
  71. package/dist/reporting.d.ts.map +1 -0
  72. package/dist/reporting.js +229 -0
  73. package/dist/reporting.js.map +1 -0
  74. package/dist/safety/denyRules.d.ts +57 -0
  75. package/dist/safety/denyRules.d.ts.map +1 -0
  76. package/dist/safety/denyRules.js +498 -0
  77. package/dist/safety/denyRules.js.map +1 -0
  78. package/dist/safety/envClassifier.d.ts +45 -0
  79. package/dist/safety/envClassifier.d.ts.map +1 -0
  80. package/dist/safety/envClassifier.js +99 -0
  81. package/dist/safety/envClassifier.js.map +1 -0
  82. package/dist/safety/index.d.ts +83 -0
  83. package/dist/safety/index.d.ts.map +1 -0
  84. package/dist/safety/index.js +184 -0
  85. package/dist/safety/index.js.map +1 -0
  86. package/dist/safety/limitSignals.d.ts +36 -0
  87. package/dist/safety/limitSignals.d.ts.map +1 -0
  88. package/dist/safety/limitSignals.js +79 -0
  89. package/dist/safety/limitSignals.js.map +1 -0
  90. package/dist/safety/paths.d.ts +32 -0
  91. package/dist/safety/paths.d.ts.map +1 -0
  92. package/dist/safety/paths.js +102 -0
  93. package/dist/safety/paths.js.map +1 -0
  94. package/dist/safety/redact.d.ts +17 -0
  95. package/dist/safety/redact.d.ts.map +1 -0
  96. package/dist/safety/redact.js +85 -0
  97. package/dist/safety/redact.js.map +1 -0
  98. package/dist/spawnEnv.d.ts +18 -0
  99. package/dist/spawnEnv.d.ts.map +1 -0
  100. package/dist/spawnEnv.js +43 -0
  101. package/dist/spawnEnv.js.map +1 -0
  102. package/dist/store/auditLog.d.ts +28 -0
  103. package/dist/store/auditLog.d.ts.map +1 -0
  104. package/dist/store/auditLog.js +48 -0
  105. package/dist/store/auditLog.js.map +1 -0
  106. package/dist/store/index.d.ts +140 -0
  107. package/dist/store/index.d.ts.map +1 -0
  108. package/dist/store/index.js +648 -0
  109. package/dist/store/index.js.map +1 -0
  110. package/dist/store/schema.d.ts +17 -0
  111. package/dist/store/schema.d.ts.map +1 -0
  112. package/dist/store/schema.js +197 -0
  113. package/dist/store/schema.js.map +1 -0
  114. package/dist/system.d.ts +69 -0
  115. package/dist/system.d.ts.map +1 -0
  116. package/dist/system.js +135 -0
  117. package/dist/system.js.map +1 -0
  118. package/dist/workerManager.d.ts +80 -0
  119. package/dist/workerManager.d.ts.map +1 -0
  120. package/dist/workerManager.js +280 -0
  121. package/dist/workerManager.js.map +1 -0
  122. package/dist/worktrees.d.ts +74 -0
  123. package/dist/worktrees.d.ts.map +1 -0
  124. package/dist/worktrees.js +210 -0
  125. package/dist/worktrees.js.map +1 -0
  126. package/package.json +48 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envClassifier.js","sourceRoot":"","sources":["../../src/safety/envClassifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Safety layer (§8): environment classifier + `PreToolUse` hook + `canUseTool`
3
+ * callback + mode floor. Registered on *every* worker unconditionally — the
4
+ * Worker Manager takes one by construction and cannot build a worker without it.
5
+ *
6
+ * Defense in depth: the `PreToolUse` hook is the hard guarantee (it runs before
7
+ * the rest of the permission chain and applies to subagents too), and
8
+ * `canUseTool` is policy refinement on top. Each is independently sufficient to
9
+ * stop the canonical accident.
10
+ */
11
+ import type { CanUseTool, HookCallbackMatcher, HookEvent } from "@anthropic-ai/claude-agent-sdk";
12
+ import type { AuditEvent, Environment, OrchestratorConfig, Ulid } from "@orc-brain/shared";
13
+ import { type DenyDecision } from "./denyRules.js";
14
+ /** Sink that persists audit events to the append-only JSONL log (§8.6). */
15
+ export interface AuditSink {
16
+ record(event: AuditEvent): void;
17
+ }
18
+ /** Receives blocked tool calls for escalation accounting (§8.5). */
19
+ export interface DenialReporter {
20
+ recordDenial(d: {
21
+ run_id: string | null;
22
+ task_id: string | null;
23
+ rule_id: string;
24
+ tool_name: string;
25
+ input_summary: string;
26
+ stated_intent?: string | null;
27
+ }): unknown;
28
+ }
29
+ /** Per-worker context the safety layer needs to gate that worker's tool calls. */
30
+ export interface ScopeSafetyContext {
31
+ run_id: Ulid | null;
32
+ task_id: Ulid | null;
33
+ session_id?: string | null;
34
+ environment: Environment;
35
+ cwd: string;
36
+ path_allowlist: string[];
37
+ path_denylist: string[];
38
+ }
39
+ /**
40
+ * The safety layer. One instance per orchestrator; it produces per-worker hook
41
+ * and `canUseTool` closures bound to a {@link ScopeSafetyContext}.
42
+ */
43
+ export declare class SafetyLayer {
44
+ private readonly config;
45
+ private readonly audit;
46
+ /** Optional escalation sink; denials are reported for blocking (§8.5). */
47
+ private readonly escalation?;
48
+ /** Enforcement can never be silently disabled — always true in this build. */
49
+ readonly enabled = true;
50
+ constructor(config: OrchestratorConfig, audit: AuditSink,
51
+ /** Optional escalation sink; denials are reported for blocking (§8.5). */
52
+ escalation?: DenialReporter | undefined);
53
+ /**
54
+ * Structural mode floor (§8.3). `bypassPermissions` is unrepresentable in the
55
+ * Scope type; this asserts the invariant a second time at worker-build time.
56
+ */
57
+ assertPermissionMode(mode: string): void;
58
+ /** Builds the {@link DenyContext} for a scope from its safety context. */
59
+ private denyContext;
60
+ /**
61
+ * SDK-agnostic evaluation of a single tool call (§8.2). This is the pure
62
+ * decision function; the hook and `canUseTool` closures wrap it. Unit tests
63
+ * exercise this directly without the SDK.
64
+ */
65
+ evaluateToolCall(toolName: string, input: Record<string, unknown>, ctx: ScopeSafetyContext): DenyDecision;
66
+ /** Emits an audit event with the tool input redacted (§8.6). */
67
+ private emit;
68
+ /**
69
+ * Builds the `PreToolUse` hook for a worker (§8.2). This is the guarantee: it
70
+ * runs before the rest of the permission chain and denies via
71
+ * `permissionDecision: "deny"`, which holds even under `acceptEdits`.
72
+ */
73
+ buildHooks(ctx: ScopeSafetyContext): Partial<Record<HookEvent, HookCallbackMatcher[]>>;
74
+ /**
75
+ * Builds the `canUseTool` callback (§8.3): the second, runtime layer. It
76
+ * re-checks the same decision and can rewrite inputs. Hooks are the
77
+ * guarantee; this is policy refinement and defense in depth.
78
+ */
79
+ buildCanUseTool(ctx: ScopeSafetyContext): CanUseTool;
80
+ }
81
+ /** Constructs the singleton safety layer (§8). */
82
+ export declare function createSafetyLayer(config: OrchestratorConfig, audit: AuditSink): SafetyLayer;
83
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/safety/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,UAAU,EAEV,mBAAmB,EACnB,SAAS,EAEV,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,UAAU,EAEV,WAAW,EACX,kBAAkB,EAClB,IAAI,EACL,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAIxB,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CACjC;AAED,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,CAAC,EAAE;QACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,GAAG,OAAO,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,WAAW,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAkBD;;;GAGG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAP9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,QAAQ;gBAGL,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,SAAS;IACjC,0EAA0E;IACzD,UAAU,CAAC,EAAE,cAAc,YAAA;IAG9C;;;OAGG;IACH,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IASxC,0EAA0E;IAC1E,OAAO,CAAC,WAAW;IAUnB;;;;OAIG;IACH,gBAAgB,CACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,EAAE,kBAAkB,GACtB,YAAY;IAoCf,gEAAgE;IAChE,OAAO,CAAC,IAAI;IAyBZ;;;;OAIG;IACH,UAAU,CACR,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAwDpD;;;;OAIG;IACH,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,UAAU;CAwBrD;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,SAAS,GACf,WAAW,CAEb"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Safety layer (§8): environment classifier + `PreToolUse` hook + `canUseTool`
3
+ * callback + mode floor. Registered on *every* worker unconditionally — the
4
+ * Worker Manager takes one by construction and cannot build a worker without it.
5
+ *
6
+ * Defense in depth: the `PreToolUse` hook is the hard guarantee (it runs before
7
+ * the rest of the permission chain and applies to subagents too), and
8
+ * `canUseTool` is policy refinement on top. Each is independently sufficient to
9
+ * stop the canonical accident.
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ import { evaluateBash, evaluatePathRead, evaluatePathWrite, } from "./denyRules.js";
13
+ import { isProductionLike } from "./envClassifier.js";
14
+ import { redactValue } from "./redact.js";
15
+ /** Tools that write to the filesystem and are checked against the allowlist. */
16
+ const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
17
+ /** SHA-256 hash of a tool input, for correlating audit rows without the payload. */
18
+ function hashInput(input) {
19
+ return createHash("sha256")
20
+ .update(JSON.stringify(input) ?? "")
21
+ .digest("hex");
22
+ }
23
+ /** A single deny reason mapped from a rule match, or null when allowed. */
24
+ function reasonText(decision) {
25
+ const m = decision.match;
26
+ return m ? `${m.rule_id} (${m.rule_class}): ${m.reason}` : "policy";
27
+ }
28
+ /**
29
+ * The safety layer. One instance per orchestrator; it produces per-worker hook
30
+ * and `canUseTool` closures bound to a {@link ScopeSafetyContext}.
31
+ */
32
+ export class SafetyLayer {
33
+ config;
34
+ audit;
35
+ escalation;
36
+ /** Enforcement can never be silently disabled — always true in this build. */
37
+ enabled = true;
38
+ constructor(config, audit,
39
+ /** Optional escalation sink; denials are reported for blocking (§8.5). */
40
+ escalation) {
41
+ this.config = config;
42
+ this.audit = audit;
43
+ this.escalation = escalation;
44
+ }
45
+ /**
46
+ * Structural mode floor (§8.3). `bypassPermissions` is unrepresentable in the
47
+ * Scope type; this asserts the invariant a second time at worker-build time.
48
+ */
49
+ assertPermissionMode(mode) {
50
+ if (mode === "bypassPermissions" || mode === "dontAsk" || mode === "auto") {
51
+ throw new Error(`SafetyLayer: permission mode '${mode}' is not permitted (§8.3). ` +
52
+ `Only 'plan', 'default', and 'acceptEdits' may reach a worker.`);
53
+ }
54
+ }
55
+ /** Builds the {@link DenyContext} for a scope from its safety context. */
56
+ denyContext(ctx) {
57
+ return {
58
+ environment: ctx.environment,
59
+ cwd: ctx.cwd,
60
+ path_allowlist: ctx.path_allowlist,
61
+ path_denylist: ctx.path_denylist,
62
+ dev_posture: this.config.safety.dev_posture,
63
+ };
64
+ }
65
+ /**
66
+ * SDK-agnostic evaluation of a single tool call (§8.2). This is the pure
67
+ * decision function; the hook and `canUseTool` closures wrap it. Unit tests
68
+ * exercise this directly without the SDK.
69
+ */
70
+ evaluateToolCall(toolName, input, ctx) {
71
+ const deny = this.denyContext(ctx);
72
+ if (toolName === "Bash") {
73
+ const command = typeof input.command === "string" ? input.command : "";
74
+ return evaluateBash(command, deny);
75
+ }
76
+ if (WRITE_TOOLS.has(toolName)) {
77
+ const fp = input.file_path ??
78
+ input.notebook_path ??
79
+ input.path ??
80
+ "";
81
+ return evaluatePathWrite(fp, deny);
82
+ }
83
+ if (toolName === "Read") {
84
+ const fp = input.file_path ?? input.path ?? "";
85
+ return evaluatePathRead(fp, deny);
86
+ }
87
+ // MCP tools: unknown MCP tool in a production scope ⇒ deny (§8.2).
88
+ if (toolName.startsWith("mcp__")) {
89
+ if (isProductionLike(ctx.environment)) {
90
+ return {
91
+ verdict: "deny",
92
+ match: {
93
+ rule_id: "MCP-1",
94
+ rule_class: "infra",
95
+ reason: "unclassified MCP tool in production scope",
96
+ },
97
+ };
98
+ }
99
+ return { verdict: "allow_with_audit", match: null };
100
+ }
101
+ return { verdict: "allow", match: null };
102
+ }
103
+ /** Emits an audit event with the tool input redacted (§8.6). */
104
+ emit(kind, ctx, toolName, input, decision, ruleId, detail = null) {
105
+ const event = {
106
+ ts: new Date().toISOString(),
107
+ run_id: ctx.run_id,
108
+ task_id: ctx.task_id,
109
+ session_id: ctx.session_id ?? null,
110
+ kind,
111
+ tool_name: toolName,
112
+ tool_input_hash: input === null ? null : hashInput(input),
113
+ tool_input: redactValue(input),
114
+ decision,
115
+ rule_id: ruleId,
116
+ detail,
117
+ };
118
+ this.audit.record(event);
119
+ }
120
+ /**
121
+ * Builds the `PreToolUse` hook for a worker (§8.2). This is the guarantee: it
122
+ * runs before the rest of the permission chain and denies via
123
+ * `permissionDecision: "deny"`, which holds even under `acceptEdits`.
124
+ */
125
+ buildHooks(ctx) {
126
+ const hook = async (rawInput) => {
127
+ if (rawInput.hook_event_name !== "PreToolUse")
128
+ return { continue: true };
129
+ const toolName = rawInput.tool_name;
130
+ const input = (rawInput.tool_input ?? {});
131
+ const decision = this.evaluateToolCall(toolName, input, ctx);
132
+ // Every tool call is audited (§8.6).
133
+ this.emit("tool_call", ctx, toolName, input, decision.verdict, decision.match?.rule_id ?? null);
134
+ if (decision.verdict === "deny" ||
135
+ decision.verdict === "require_approval") {
136
+ this.emit("hook_block", ctx, toolName, input, "deny", decision.match?.rule_id ?? null, { reason: reasonText(decision) });
137
+ // Report the denial for escalation accounting (§8.5). The hook is the
138
+ // guarantee layer and runs before canUseTool, so counting here avoids
139
+ // double-counting the same blocked call.
140
+ this.escalation?.recordDenial({
141
+ run_id: ctx.run_id,
142
+ task_id: ctx.task_id,
143
+ rule_id: decision.match?.rule_id ?? "policy",
144
+ tool_name: toolName,
145
+ input_summary: JSON.stringify(redactValue(input)).slice(0, 200),
146
+ });
147
+ return {
148
+ hookSpecificOutput: {
149
+ hookEventName: "PreToolUse",
150
+ permissionDecision: "deny",
151
+ permissionDecisionReason: `Blocked by orc-brain safety rule ${reasonText(decision)}. ` +
152
+ `Do not retry; propose an alternative or mark blocked (§8.5).`,
153
+ },
154
+ };
155
+ }
156
+ return { continue: true };
157
+ };
158
+ return { PreToolUse: [{ hooks: [hook] }] };
159
+ }
160
+ /**
161
+ * Builds the `canUseTool` callback (§8.3): the second, runtime layer. It
162
+ * re-checks the same decision and can rewrite inputs. Hooks are the
163
+ * guarantee; this is policy refinement and defense in depth.
164
+ */
165
+ buildCanUseTool(ctx) {
166
+ return async (toolName, input) => {
167
+ const decision = this.evaluateToolCall(toolName, input, ctx);
168
+ if (decision.verdict === "deny" ||
169
+ decision.verdict === "require_approval") {
170
+ this.emit("permission_deny", ctx, toolName, input, "deny", decision.match?.rule_id ?? null, { reason: reasonText(decision) });
171
+ return {
172
+ behavior: "deny",
173
+ message: `orc-brain: ${reasonText(decision)}`,
174
+ };
175
+ }
176
+ return { behavior: "allow", updatedInput: input };
177
+ };
178
+ }
179
+ }
180
+ /** Constructs the singleton safety layer (§8). */
181
+ export function createSafetyLayer(config, audit) {
182
+ return new SafetyLayer(config, audit);
183
+ }
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/safety/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Rate-limit signal detection (§7.4). The CLI surfaces limits as human-readable
3
+ * error text ("You've hit your session limit · resets 3:45pm", "…weekly
4
+ * limit…", "…Opus limit"). There is no confirmed structured error code, so
5
+ * detection is pattern-matching, isolated here with patterns in config. When
6
+ * nothing matches but a worker failed with a 429-ish shape, we return
7
+ * `unknown_limit` and back off anyway — conservative by design.
8
+ */
9
+ import type { LimitConfig, ModelName } from "@orc-brain/shared";
10
+ /** Kind of limit detected. */
11
+ export type LimitKind = "session_limit" | "weekly_limit" | "model_limit" | "unknown_limit";
12
+ /** A detected rate-limit signal. */
13
+ export interface LimitSignal {
14
+ kind: LimitKind;
15
+ /** Which model is quarantined, for `model_limit` (router R7). */
16
+ model?: ModelName;
17
+ /** Parsed reset time (epoch ms), if the text carried one. */
18
+ resets_at?: number;
19
+ /** The raw matched text, for surfacing in UI/reports. */
20
+ raw: string;
21
+ }
22
+ /**
23
+ * Parses a reset clock like "resets 3:45pm" into an epoch-ms timestamp on or
24
+ * after `now`. Returns undefined when no time is present. `now` is injected so
25
+ * the function stays pure and testable.
26
+ */
27
+ export declare function parseResetTime(text: string, now: Date): number | undefined;
28
+ /**
29
+ * Inspects worker error text for a rate-limit signal (§7.4). `httpish429`
30
+ * marks a failure that looked like a 429 even if no pattern matched, forcing a
31
+ * conservative `unknown_limit` back-off.
32
+ */
33
+ export declare function detectLimitSignal(text: string, config: LimitConfig, now: Date, httpish429?: boolean): LimitSignal | null;
34
+ /** Computes the next back-off delay (ms) for the Nth consecutive limit hit. */
35
+ export declare function backoffDelayMs(attempt: number, config: LimitConfig): number;
36
+ //# sourceMappingURL=limitSignals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limitSignals.d.ts","sourceRoot":"","sources":["../../src/safety/limitSignals.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEhE,8BAA8B;AAC9B,MAAM,MAAM,SAAS,GACnB,eAAe,GAAG,cAAc,GAAG,aAAa,GAAG,eAAe,CAAC;AAErE,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;CACb;AAUD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAe1E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,IAAI,EACT,UAAU,UAAQ,GACjB,WAAW,GAAG,IAAI,CA8BpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAM3E"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Rate-limit signal detection (§7.4). The CLI surfaces limits as human-readable
3
+ * error text ("You've hit your session limit · resets 3:45pm", "…weekly
4
+ * limit…", "…Opus limit"). There is no confirmed structured error code, so
5
+ * detection is pattern-matching, isolated here with patterns in config. When
6
+ * nothing matches but a worker failed with a 429-ish shape, we return
7
+ * `unknown_limit` and back off anyway — conservative by design.
8
+ */
9
+ const MODEL_NAMES = ["opus", "sonnet", "haiku"];
10
+ /** Extracts a model name mentioned in the error text, if any. */
11
+ function extractModel(text) {
12
+ const lower = text.toLowerCase();
13
+ return MODEL_NAMES.find((m) => lower.includes(`${m} limit`));
14
+ }
15
+ /**
16
+ * Parses a reset clock like "resets 3:45pm" into an epoch-ms timestamp on or
17
+ * after `now`. Returns undefined when no time is present. `now` is injected so
18
+ * the function stays pure and testable.
19
+ */
20
+ export function parseResetTime(text, now) {
21
+ const m = text.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i);
22
+ if (!m)
23
+ return undefined;
24
+ let hour = Number(m[1]);
25
+ const minute = m[2] ? Number(m[2]) : 0;
26
+ const ampm = m[3]?.toLowerCase();
27
+ if (ampm === "pm" && hour < 12)
28
+ hour += 12;
29
+ if (ampm === "am" && hour === 12)
30
+ hour = 0;
31
+ const reset = new Date(now);
32
+ reset.setHours(hour, minute, 0, 0);
33
+ // If the computed time already passed today, it must be tomorrow.
34
+ if (reset.getTime() <= now.getTime())
35
+ reset.setDate(reset.getDate() + 1);
36
+ return reset.getTime();
37
+ }
38
+ /**
39
+ * Inspects worker error text for a rate-limit signal (§7.4). `httpish429`
40
+ * marks a failure that looked like a 429 even if no pattern matched, forcing a
41
+ * conservative `unknown_limit` back-off.
42
+ */
43
+ export function detectLimitSignal(text, config, now, httpish429 = false) {
44
+ const model = new RegExp(config.patterns.model_limit, "i").exec(text);
45
+ if (model) {
46
+ return {
47
+ kind: "model_limit",
48
+ model: extractModel(text),
49
+ resets_at: parseResetTime(text, now),
50
+ raw: model[0],
51
+ };
52
+ }
53
+ const weekly = new RegExp(config.patterns.weekly_limit, "i").exec(text);
54
+ if (weekly) {
55
+ return {
56
+ kind: "weekly_limit",
57
+ resets_at: parseResetTime(text, now),
58
+ raw: weekly[0],
59
+ };
60
+ }
61
+ const session = new RegExp(config.patterns.session_limit, "i").exec(text);
62
+ if (session) {
63
+ return {
64
+ kind: "session_limit",
65
+ resets_at: parseResetTime(text, now),
66
+ raw: session[0],
67
+ };
68
+ }
69
+ if (httpish429) {
70
+ return { kind: "unknown_limit", raw: text.slice(0, 200) };
71
+ }
72
+ return null;
73
+ }
74
+ /** Computes the next back-off delay (ms) for the Nth consecutive limit hit. */
75
+ export function backoffDelayMs(attempt, config) {
76
+ const idx = Math.min(attempt, config.backoff_ms.length - 1);
77
+ return Math.min(config.backoff_ms[idx] ?? config.backoff_cap_ms, config.backoff_cap_ms);
78
+ }
79
+ //# sourceMappingURL=limitSignals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limitSignals.js","sourceRoot":"","sources":["../../src/safety/limitSignals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Path / glob helpers for allowlist enforcement (§8.2, §8.3). Write/Edit targets
3
+ * and Bash file arguments are resolved to absolute paths and checked against the
4
+ * scope's `path_allowlist` / `path_denylist` independently of any tool mode.
5
+ */
6
+ /** Expands a leading `~` to the user's home directory. */
7
+ export declare function expandHome(p: string): string;
8
+ /** Resolves `p` to an absolute path relative to `cwd`, expanding `~`. */
9
+ export declare function toAbsolute(p: string, cwd: string): string;
10
+ /**
11
+ * Converts a glob to an anchored RegExp. Supports `**` (any depth, including
12
+ * across `/`), `*` (within a segment), and `?`. Globs are matched against
13
+ * absolute paths, so a bare glob is first resolved against `cwd`.
14
+ */
15
+ export declare function globToRegExp(glob: string, cwd: string): RegExp;
16
+ /** True when the absolute `path` is inside directory `dir` (or equal to it). */
17
+ export declare function isInside(path: string, dir: string): boolean;
18
+ /**
19
+ * True when a glob pattern matches `absPath`. A pattern that names a directory
20
+ * (no wildcard) also matches everything beneath it, so `src` covers `src/a.ts`.
21
+ */
22
+ export declare function globMatches(pattern: string, absPath: string, cwd: string): boolean;
23
+ /**
24
+ * Allowlist/denylist decision for an absolute path (§8.2). Denylist overrides
25
+ * allowlist. An empty allowlist denies everything (least privilege).
26
+ */
27
+ export declare function isPathAllowed(absPath: string, allowlist: string[], denylist: string[], cwd: string): boolean;
28
+ /** True when `absPath` is a sensitive credential file (§8.2 credential class). */
29
+ export declare function isCredentialPath(absPath: string): boolean;
30
+ /** True when `absPath` is a dotenv file (`.env`, `.env.production`, …). */
31
+ export declare function isDotenvPath(absPath: string): boolean;
32
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/safety/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAuB9D;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAI3D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAOT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAGT;AAcD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAErD"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Path / glob helpers for allowlist enforcement (§8.2, §8.3). Write/Edit targets
3
+ * and Bash file arguments are resolved to absolute paths and checked against the
4
+ * scope's `path_allowlist` / `path_denylist` independently of any tool mode.
5
+ */
6
+ import { isAbsolute, resolve } from "node:path";
7
+ import { homedir } from "node:os";
8
+ /** Expands a leading `~` to the user's home directory. */
9
+ export function expandHome(p) {
10
+ if (p === "~")
11
+ return homedir();
12
+ if (p.startsWith("~/"))
13
+ return resolve(homedir(), p.slice(2));
14
+ return p;
15
+ }
16
+ /** Resolves `p` to an absolute path relative to `cwd`, expanding `~`. */
17
+ export function toAbsolute(p, cwd) {
18
+ const expanded = expandHome(p);
19
+ return isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
20
+ }
21
+ /**
22
+ * Converts a glob to an anchored RegExp. Supports `**` (any depth, including
23
+ * across `/`), `*` (within a segment), and `?`. Globs are matched against
24
+ * absolute paths, so a bare glob is first resolved against `cwd`.
25
+ */
26
+ export function globToRegExp(glob, cwd) {
27
+ const abs = toAbsolute(glob, cwd);
28
+ let re = "";
29
+ for (let i = 0; i < abs.length; i++) {
30
+ const c = abs[i];
31
+ if (c === "*") {
32
+ if (abs[i + 1] === "*") {
33
+ // `**` → any chars including path separators; consume an optional `/`.
34
+ re += ".*";
35
+ i++;
36
+ if (abs[i + 1] === "/")
37
+ i++;
38
+ }
39
+ else {
40
+ re += "[^/]*";
41
+ }
42
+ }
43
+ else if (c === "?") {
44
+ re += "[^/]";
45
+ }
46
+ else if (".+^${}()|[]\\".includes(c)) {
47
+ re += "\\" + c;
48
+ }
49
+ else {
50
+ re += c;
51
+ }
52
+ }
53
+ return new RegExp(`^${re}$`);
54
+ }
55
+ /** True when the absolute `path` is inside directory `dir` (or equal to it). */
56
+ export function isInside(path, dir) {
57
+ const p = resolve(path);
58
+ const d = resolve(dir);
59
+ return p === d || p.startsWith(d.endsWith("/") ? d : d + "/");
60
+ }
61
+ /**
62
+ * True when a glob pattern matches `absPath`. A pattern that names a directory
63
+ * (no wildcard) also matches everything beneath it, so `src` covers `src/a.ts`.
64
+ */
65
+ export function globMatches(pattern, absPath, cwd) {
66
+ if (globToRegExp(pattern, cwd).test(absPath))
67
+ return true;
68
+ // Directory-prefix semantics for non-wildcard patterns.
69
+ if (!/[*?]/.test(pattern)) {
70
+ return isInside(absPath, toAbsolute(pattern, cwd));
71
+ }
72
+ return false;
73
+ }
74
+ /**
75
+ * Allowlist/denylist decision for an absolute path (§8.2). Denylist overrides
76
+ * allowlist. An empty allowlist denies everything (least privilege).
77
+ */
78
+ export function isPathAllowed(absPath, allowlist, denylist, cwd) {
79
+ if (denylist.some((g) => globMatches(g, absPath, cwd)))
80
+ return false;
81
+ return allowlist.some((g) => globMatches(g, absPath, cwd));
82
+ }
83
+ /** Credential paths that are never readable regardless of environment (§8.2). */
84
+ const CREDENTIAL_PATTERNS = [
85
+ /(^|\/)\.ssh(\/|$)/,
86
+ /(^|\/)\.aws\/credentials$/,
87
+ /(^|\/)\.aws\/config$/,
88
+ /(^|\/)\.gnupg(\/|$)/,
89
+ /(^|\/)\.netrc$/,
90
+ /(^|\/)\.docker\/config\.json$/,
91
+ /(^|\/)\.kube\/config$/,
92
+ /id_rsa|id_ed25519|\.pem$|\.p12$|\.pfx$/,
93
+ ];
94
+ /** True when `absPath` is a sensitive credential file (§8.2 credential class). */
95
+ export function isCredentialPath(absPath) {
96
+ return CREDENTIAL_PATTERNS.some((re) => re.test(absPath));
97
+ }
98
+ /** True when `absPath` is a dotenv file (`.env`, `.env.production`, …). */
99
+ export function isDotenvPath(absPath) {
100
+ return /(^|\/)\.env(\.[^/]+)?$/.test(absPath);
101
+ }
102
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/safety/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Secret scrubber (§8.6). Tool inputs are redacted before they are written to
3
+ * the append-only audit log. Pattern-based and conservative: it would rather
4
+ * over-redact than leak a token.
5
+ */
6
+ /** Registers a literal value for redaction (spec 003 §R5). Short values are ignored. */
7
+ export declare function registerSecretValue(value: string): void;
8
+ /** Clears runtime-registered secret values. Test-only. */
9
+ export declare function clearRegisteredSecretValues(): void;
10
+ /** Redacts secrets from a plain string. */
11
+ export declare function redactString(input: string): string;
12
+ /**
13
+ * Deep-redacts secrets from an arbitrary JSON-serializable value, returning a
14
+ * structurally-identical copy with string leaves scrubbed (§8.6).
15
+ */
16
+ export declare function redactValue(value: unknown): unknown;
17
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/safety/redact.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6CH,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,0DAA0D;AAC1D,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED,2CAA2C;AAC3C,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASnD"}