pi-harness-runtime 1.1.150 → 1.1.152

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 (158) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/harness/key-loader.ts +84 -0
  3. package/harness/langchain/agents.ts +65 -15
  4. package/harness/langchain/daemon.ts +58 -30
  5. package/harness/langchain/graph.ts +55 -7
  6. package/harness/langchain/jev-logger.ts +274 -0
  7. package/harness/langchain/run.ts +64 -161
  8. package/harness/langchain/surge.test.ts +42 -0
  9. package/harness/langchain/surge.ts +22 -1
  10. package/index.ts +24 -0
  11. package/package.json +1 -1
  12. package/packages/a2a-adapter/package.json +1 -1
  13. package/packages/agent-policy/dist/extension.d.ts +19 -0
  14. package/packages/agent-policy/dist/extension.d.ts.map +1 -0
  15. package/packages/agent-policy/dist/extension.js +322 -0
  16. package/packages/agent-policy/dist/extension.js.map +1 -0
  17. package/packages/agent-policy/dist/index.d.ts +25 -0
  18. package/packages/agent-policy/dist/index.d.ts.map +1 -0
  19. package/packages/agent-policy/dist/index.js +27 -0
  20. package/packages/agent-policy/dist/index.js.map +1 -0
  21. package/packages/agent-policy/dist/policy-loader.d.ts +90 -0
  22. package/packages/agent-policy/dist/policy-loader.d.ts.map +1 -0
  23. package/packages/agent-policy/dist/policy-loader.js +331 -0
  24. package/packages/agent-policy/dist/policy-loader.js.map +1 -0
  25. package/packages/agent-policy/dist/policy-renderer.d.ts +42 -0
  26. package/packages/agent-policy/dist/policy-renderer.d.ts.map +1 -0
  27. package/packages/agent-policy/dist/policy-renderer.js +295 -0
  28. package/packages/agent-policy/dist/policy-renderer.js.map +1 -0
  29. package/packages/agent-policy/dist/policy-store.d.ts +99 -0
  30. package/packages/agent-policy/dist/policy-store.d.ts.map +1 -0
  31. package/packages/agent-policy/dist/policy-store.js +244 -0
  32. package/packages/agent-policy/dist/policy-store.js.map +1 -0
  33. package/packages/agent-policy/dist/types.d.ts +237 -0
  34. package/packages/agent-policy/dist/types.d.ts.map +1 -0
  35. package/packages/agent-policy/dist/types.js +130 -0
  36. package/packages/agent-policy/dist/types.js.map +1 -0
  37. package/packages/agent-policy/package.json +45 -0
  38. package/packages/agent-policy/src/extension.ts +413 -0
  39. package/packages/agent-policy/src/index.ts +52 -0
  40. package/packages/agent-policy/src/policy-loader.ts +398 -0
  41. package/packages/agent-policy/src/policy-renderer.ts +362 -0
  42. package/packages/agent-policy/src/policy-store.ts +319 -0
  43. package/packages/agent-policy/src/types.ts +385 -0
  44. package/packages/agent-policy/test/policy.test.ts +383 -0
  45. package/packages/architecture-generator/package.json +1 -1
  46. package/packages/auth/package.json +1 -1
  47. package/packages/autonomous-refactor/package.json +1 -1
  48. package/packages/autonomous-runtime/package.json +1 -1
  49. package/packages/cache-strategy/package.json +1 -1
  50. package/packages/capability-registry/package.json +1 -1
  51. package/packages/checkpoint/package.json +1 -1
  52. package/packages/cli-plugin-sdk/package.json +1 -1
  53. package/packages/clipboard/package.json +1 -1
  54. package/packages/clipboard-plugin/package.json +1 -1
  55. package/packages/code-generation/package.json +1 -1
  56. package/packages/code-review/package.json +1 -1
  57. package/packages/codex-adapter/package.json +1 -1
  58. package/packages/codex-watcher/dist/src/parser.d.ts.map +1 -1
  59. package/packages/codex-watcher/dist/src/parser.js +35 -0
  60. package/packages/codex-watcher/dist/src/parser.js.map +1 -1
  61. package/packages/codex-watcher/package.json +1 -1
  62. package/packages/codex-watcher/src/parser.ts +41 -0
  63. package/packages/config-capture/package.json +1 -1
  64. package/packages/context-compiler/package.json +1 -1
  65. package/packages/context-discovery/package.json +1 -1
  66. package/packages/context-manager/package.json +1 -1
  67. package/packages/cookie-sanitizer/package.json +1 -1
  68. package/packages/cost-optimizer/package.json +1 -1
  69. package/packages/dependency-analyzer/package.json +1 -1
  70. package/packages/django-plugin/package.json +1 -1
  71. package/packages/doc-generator/package.json +1 -1
  72. package/packages/evaluation-engine/package.json +1 -1
  73. package/packages/evaluation-runner/package.json +1 -1
  74. package/packages/event-bus/package.json +1 -1
  75. package/packages/event-store/package.json +1 -1
  76. package/packages/experience-replay/package.json +1 -1
  77. package/packages/feedback-collector/package.json +1 -1
  78. package/packages/file-copy-helper/package.json +1 -1
  79. package/packages/framework-detector/package.json +1 -1
  80. package/packages/framework-plugin-sdk/package.json +1 -1
  81. package/packages/frappe-plugin/package.json +1 -1
  82. package/packages/generic-web-plugin/package.json +1 -1
  83. package/packages/health-monitor/package.json +1 -1
  84. package/packages/intent-analyzer/package.json +1 -1
  85. package/packages/jev-judge/package.json +1 -1
  86. package/packages/knowledge-graph/package.json +1 -1
  87. package/packages/knowledge-retrieval/package.json +1 -1
  88. package/packages/laravel-plugin/package.json +1 -1
  89. package/packages/learning-engine/package.json +1 -1
  90. package/packages/mcp-adapter/package.json +1 -1
  91. package/packages/memory-engine/package.json +1 -1
  92. package/packages/milestone-manager/package.json +1 -1
  93. package/packages/model-registry/package.json +1 -1
  94. package/packages/nextjs-plugin/package.json +1 -1
  95. package/packages/notification/dist/adapters/telegram-adapter.d.ts +5 -0
  96. package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
  97. package/packages/notification/dist/adapters/telegram-adapter.js +54 -0
  98. package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
  99. package/packages/notification/dist/notification-center.d.ts +13 -0
  100. package/packages/notification/dist/notification-center.d.ts.map +1 -1
  101. package/packages/notification/dist/notification-center.js +36 -0
  102. package/packages/notification/dist/notification-center.js.map +1 -1
  103. package/packages/notification/package.json +1 -1
  104. package/packages/notification/src/adapters/telegram-adapter.ts +71 -0
  105. package/packages/notification/src/notification-center.ts +53 -0
  106. package/packages/observability/package.json +1 -1
  107. package/packages/okf-indexer/package.json +1 -1
  108. package/packages/paperclip-integration/package.json +1 -1
  109. package/packages/performance-optimizer/package.json +1 -1
  110. package/packages/privilege-broker/package.json +1 -1
  111. package/packages/project-analyzer/package.json +1 -1
  112. package/packages/project-bootstrap/package.json +1 -1
  113. package/packages/projection-engine/package.json +1 -1
  114. package/packages/prompt-compiler/package.json +1 -1
  115. package/packages/prompt-versioning/package.json +1 -1
  116. package/packages/provider-adapter-sdk/package.json +1 -1
  117. package/packages/provider-router/package.json +1 -1
  118. package/packages/provider-selector/package.json +1 -1
  119. package/packages/providers/package.json +1 -1
  120. package/packages/qdrant-skills/package.json +1 -1
  121. package/packages/quota-manager/package.json +1 -1
  122. package/packages/rate-limiter/package.json +1 -1
  123. package/packages/react-vite-plugin/package.json +1 -1
  124. package/packages/release-manager/package.json +1 -1
  125. package/packages/requirement-compiler/package.json +1 -1
  126. package/packages/runtime/package.json +1 -1
  127. package/packages/scheduler/package.json +1 -1
  128. package/packages/scheduler-adapter/package.json +1 -1
  129. package/packages/session/package.json +1 -1
  130. package/packages/session-api/package.json +1 -1
  131. package/packages/session-export/package.json +1 -1
  132. package/packages/shared-context/package.json +1 -1
  133. package/packages/skill-mcp-client/package.json +1 -1
  134. package/packages/skill-registry/package.json +1 -1
  135. package/packages/skills/package.json +1 -1
  136. package/packages/sprint-planner/package.json +1 -1
  137. package/packages/subscription-engine/package.json +1 -1
  138. package/packages/task-compiler/package.json +1 -1
  139. package/packages/tencentdb-memory/package.json +1 -1
  140. package/packages/tencentdb-sync/package.json +1 -1
  141. package/packages/test-data-generator/package.json +1 -1
  142. package/packages/test-generator/package.json +1 -1
  143. package/packages/todo-bd-sync/package.json +1 -1
  144. package/packages/token-estimation/package.json +1 -1
  145. package/packages/token-optimizer/package.json +1 -1
  146. package/packages/trajectory/package.json +1 -1
  147. package/packages/tui/package.json +1 -1
  148. package/packages/types/package.json +1 -1
  149. package/packages/workflow-events/package.json +1 -1
  150. package/packages/workspace-scanner/package.json +1 -1
  151. package/packages/worktree/package.json +1 -1
  152. package/packages/write-review/package.json +1 -1
  153. package/proactive-compact.ts +2 -2
  154. package/scripts/debug-codex-integration.ts +203 -0
  155. package/scripts/sync-keys-from-pi.ts +188 -0
  156. package/scripts/test-codex-session.ts +23 -0
  157. package/scripts/test-openai.ts +14 -0
  158. package/scripts/test-telegram.ts +34 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.1.139](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.150...v1.1.139) (2026-10-05)
6
+
7
+
8
+ ### Features
9
+
10
+ * **agent-policy:** Add mandatory policy delivery and mutation gating ([19d2399](https://github.com/ManotLuijiu/pi-harness-runtime/commit/19d23997d6de8e62bf97a67daf51b5cba42c9615))
11
+
5
12
  ### [1.1.138](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.147...v1.1.138) (2026-10-04)
6
13
 
7
14
  ### [1.1.75](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.95...v1.1.75) (2026-09-29)
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Key Loader — Loads API keys from ~/.pi-harness-runtime/keys/ into process.env
3
+ *
4
+ * Usage:
5
+ * import { loadKeys } from "./key-loader.js";
6
+ * loadKeys(); // Loads all keys into process.env
7
+ *
8
+ * Environment variables set:
9
+ * Telegram: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_WEBHOOK_URL, TELEGRAM_WEBHOOK_SECRET
10
+ * Models: PLANNER_API_KEY, PLANNER_MODEL, PLANNER_BASE_URL
11
+ * GLM_API_KEY
12
+ * MINIMAX_API_KEY
13
+ */
14
+
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { homedir } from "node:os";
18
+
19
+ const KEYS_DIR = join(homedir(), ".pi-harness-runtime", "keys");
20
+
21
+ const KEY_FILE_MAP: Record<string, string> = {
22
+ // Telegram notification keys
23
+ TELEGRAM_BOT_TOKEN: "telegram-bot-token.txt",
24
+ TELEGRAM_CHAT_ID: "telegram-chat-id.txt",
25
+ TELEGRAM_WEBHOOK_URL: "telegram-webhook-url.txt",
26
+ TELEGRAM_WEBHOOK_SECRET: "telegram-webhook-secret.txt",
27
+ // Model API keys (for daemon loop)
28
+ PLANNER_API_KEY: "planner-api-key.txt",
29
+ PLANNER_MODEL: "planner-model.txt",
30
+ PLANNER_BASE_URL: "planner-base-url.txt",
31
+ GLM_API_KEY: "glm-api-key.txt",
32
+ GLM_MODEL: "glm-model.txt",
33
+ GLM_BASE_URL: "glm-base-url.txt",
34
+ MINIMAX_API_KEY: "minimax-api-key.txt",
35
+ MINIMAX_MODEL: "minimax-model.txt",
36
+ MINIMAX_BASE_URL: "minimax-base-url.txt",
37
+ };
38
+
39
+ /**
40
+ * Load all keys from ~/.pi-harness-runtime/keys/ into process.env
41
+ * Only sets env vars that are not already set (allows CLI args to override)
42
+ */
43
+ export function loadKeys(): void {
44
+ if (!existsSync(KEYS_DIR)) {
45
+ console.warn(`[key-loader] Keys directory not found: ${KEYS_DIR}`);
46
+ return;
47
+ }
48
+
49
+ let loadedCount = 0;
50
+ for (const [envVar, filename] of Object.entries(KEY_FILE_MAP)) {
51
+ // Skip if already set via environment
52
+ if (process.env[envVar]) {
53
+ console.log(`[key-loader] ${envVar} already set via env, skipping`);
54
+ continue;
55
+ }
56
+
57
+ const filePath = join(KEYS_DIR, filename);
58
+ if (existsSync(filePath)) {
59
+ const value = readFileSync(filePath, "utf8").trim();
60
+ if (value) {
61
+ process.env[envVar] = value;
62
+ loadedCount++;
63
+ console.log(`[key-loader] Loaded ${envVar} from ${filename}`);
64
+ }
65
+ }
66
+ }
67
+
68
+ if (loadedCount > 0) {
69
+ console.log(`[key-loader] Loaded ${loadedCount} key(s) from ${KEYS_DIR}`);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get a specific key by name (without .txt extension)
75
+ */
76
+ export function getKey(keyName: string): string | undefined {
77
+ const filePath = join(KEYS_DIR, `${keyName}.txt`);
78
+ if (existsSync(filePath)) {
79
+ return readFileSync(filePath, "utf8").trim();
80
+ }
81
+ return undefined;
82
+ }
83
+
84
+ export default loadKeys;
@@ -37,9 +37,9 @@ export const ReviewVerdictSchema = z.object({
37
37
  comments: z
38
38
  .array(
39
39
  z.object({
40
- file: z.string().optional().describe("Affected file, if known"),
40
+ file: z.string().describe("Affected file, if known"),
41
41
  comment: z.string().describe("Actionable change request"),
42
- severity: z.enum(["critical", "major", "minor"]).optional(),
42
+ severity: z.enum(["critical", "major", "minor"]),
43
43
  }),
44
44
  )
45
45
  .describe("Comments to send back to the coder (empty when approved)"),
@@ -92,16 +92,66 @@ permission to write code until finish without waiting for confirmation.
92
92
  // - If stuck, fail with a clear error, do not wait silently
93
93
  // - Output is consumed by the loop, not a human reading for guidance
94
94
 
95
- const PLANNER_PROMPT = `You are the Planning Agent (GPT) in an autonomous daemon loop.
95
+ const PLANNER_PROMPT = `You are Jev - the intelligent Orchestrator in an autonomous daemon loop.
96
+
97
+ ## Your Role: Jev (Provider & Input Decision Engine)
98
+
99
+ You make two types of decisions:
100
+
101
+ ### 1. Provider Selection (Who should handle this?)
102
+
103
+ You have two agents available:
104
+ - **MiniMax** (default): Fast, cheap, handles 90%+ of tasks
105
+ - **GPT** (escalation): Powerful, for complex reasoning only
106
+
107
+ **Use GPT only when task involves:**
108
+ - Complex multi-file architecture decisions
109
+ - Advanced concurrency/race condition debugging
110
+ - Security-critical code requiring deep analysis
111
+ - User explicitly requests: [use-gpt] or [use-gpt-only]
112
+ - MiniMax reviewer says: "blocked" (escalate)
113
+
114
+ **Default to MiniMax** for everything else.
115
+
116
+ ### 2. User Input Decision
117
+
118
+ **Yes/No Choice** (type: "yesNo"): Binary decisions
119
+ - "Should I use Option A or Option B?"
120
+ - "Do you want me to proceed with this approach?"
121
+
122
+ **Open Question** (type: "open"): Need descriptive input
123
+ - "What would you like me to prioritize?"
124
+ - "Describe your preferred coding style"
125
+
126
+ **No User Input** (null): Proceed automatically
127
+
128
+ ## Your Task
96
129
 
97
130
  Given a feature request, produce a concise implementation plan: goals, non-goals,
98
- ordered steps, files to touch, risks, and a definition-of-done the GLM reviewer
99
- can check against.
131
+ ordered steps, files to touch, risks, and a definition-of-done.
132
+
133
+ ## Output Format
134
+
135
+ \`\`\`markdown
136
+ # Implementation Plan
137
+ ...
138
+
139
+ [JEVO]
140
+ {"provider": "minimax" | "gpt", "reason": "..."}
141
+
142
+ [USER_INPUT]
143
+ {"type": "yesNo" | "open", "question": "...", "expectedFormat?": "..."}
144
+ \`\`\`
145
+
146
+ - [JEVO] is required - always specify your provider decision
147
+ - [USER_INPUT] is optional - only if you need user input
148
+
149
+ The loop will route based on your decisions.`;
100
150
 
101
- Output markdown only. Do NOT ask for confirmation. Do NOT output "Next Steps".
102
- The loop will route automatically.`;
151
+ const CODER_PROMPT = `You are the Coding Agent in an autonomous daemon loop.
103
152
 
104
- const CODER_PROMPT = `You are the Coding Agent (MiniMax) in an autonomous daemon loop.
153
+ Jev has selected the provider for this task based on task complexity.
154
+ Write quality code regardless of which model is running.
105
155
 
106
156
  You receive a plan (and possibly review comments from a previous iteration) and
107
157
  edit the actual repository files using the available tools. Address every review
@@ -124,11 +174,11 @@ IMPORTANT:
124
174
  - If you cannot proceed, fail with a clear error message
125
175
  - Your output is consumed by the loop, not read by a human for guidance`;
126
176
 
127
- const REVIEWER_PROMPT = `You are the Master Code Review Agent (GLM) in an autonomous daemon loop.
177
+ const REVIEWER_PROMPT = `You are the Master Code Review Agent in an autonomous daemon loop.
128
178
 
129
- You are the master reviewer. You review the MiniMax coder's output against the plan.
130
- Be strict: request changes only for real problems. Output a structured verdict —
131
- the loop reads it automatically and routes MiniMax back to fix or approves.
179
+ You review the coder's output against the plan. Be strict: request changes only for
180
+ real problems. Output a structured verdict — the loop reads it automatically and
181
+ routes the coder back to fix or approves.
132
182
 
133
183
  IMPORTANT:
134
184
  - Do NOT output "Next Steps" or "Looks good, waiting for approval"
@@ -136,7 +186,7 @@ IMPORTANT:
136
186
  - If you need clarification, fail with a clear error, do not wait silently
137
187
  - Respond via the structured schema`;
138
188
 
139
- const SUPERVISOR_PROMPT = `You are the Supervisor (GPT) in an autonomous daemon loop.
189
+ const SUPERVISOR_PROMPT = `You are the Supervisor in an autonomous daemon loop.
140
190
  Coordinate the workflow automatically. The loop routes to the reviewer and back
141
191
  without human input.
142
192
 
@@ -283,7 +333,7 @@ export async function parallelReview(
283
333
  comments: unknown[],
284
334
  type: SpecialistType,
285
335
  ): {
286
- file?: string;
336
+ file: string;
287
337
  comment: string;
288
338
  severity: "critical" | "major" | "minor";
289
339
  }[] =>
@@ -294,7 +344,7 @@ export async function parallelReview(
294
344
  severity?: "critical" | "major" | "minor";
295
345
  };
296
346
  return {
297
- file: comment.file,
347
+ file: comment.file ?? "",
298
348
  comment: `[${type}] ${comment.comment}`,
299
349
  severity: comment.severity ?? "minor",
300
350
  };
@@ -320,18 +320,18 @@ class InboxWatcher {
320
320
  }
321
321
  }
322
322
 
323
- private _seedSeen(): void {
324
- if (!existsSync(this.inboxDir)) return;
325
- for (const file of readdirSync(this.inboxDir)) {
326
- if (!file.endsWith(".md")) continue;
327
- if (file.startsWith("ack-")) continue;
328
- this.seen.set(file, {
329
- taskId: file,
330
- source: "inbox",
331
- seenAt: new Date().toISOString(),
332
- });
323
+ private _seedSeen(): void {
324
+ if (!existsSync(this.inboxDir)) return;
325
+ for (const file of readdirSync(this.inboxDir)) {
326
+ if (!file.endsWith(".md")) continue;
327
+ if (file.startsWith("ack-")) continue;
328
+ this.seen.set(file, {
329
+ taskId: file,
330
+ source: "inbox",
331
+ seenAt: new Date().toISOString(),
332
+ });
333
+ }
333
334
  }
334
- }
335
335
 
336
336
  private _poll(onTask: (task: TriggeredTask) => void): void {
337
337
  if (!this.running) return;
@@ -1123,7 +1123,7 @@ export class LoopDaemon {
1123
1123
  );
1124
1124
  if (!approved) {
1125
1125
  log(`Human denied approval — parking task`);
1126
- this._notifyHumanReviewNeeded(
1126
+ await this._notifyHumanReviewNeeded(
1127
1127
  task,
1128
1128
  "Human denied approval for task start",
1129
1129
  );
@@ -1179,7 +1179,8 @@ export class LoopDaemon {
1179
1179
  }
1180
1180
  return undefined; // default MemorySaver
1181
1181
  })();
1182
- const loop: WriteReviewLoop = buildWriteReviewLoop(deps, {
1182
+ console.log(`[${loopId}] Using models - PLANNER=${process.env.PLANNER_MODEL} (${process.env.PLANNER_BASE_URL}), GLM=${process.env.GLM_MODEL} (${process.env.GLM_BASE_URL}), MINIMAX=${process.env.MINIMAX_MODEL} (${process.env.MINIMAX_BASE_URL})`);
1183
+ const loop: WriteReviewLoop = buildWriteReviewLoop(deps, {
1183
1184
  checkpointer,
1184
1185
  });
1185
1186
 
@@ -1195,7 +1196,7 @@ export class LoopDaemon {
1195
1196
  () =>
1196
1197
  loop.invoke(
1197
1198
  { request: task.request },
1198
- { configurable: { thread_id: loopId }, signal },
1199
+ { configurable: { thread_id: loopId }, signal, recursionLimit: 50 },
1199
1200
  ),
1200
1201
  {
1201
1202
  tickMs: 30_000,
@@ -1281,7 +1282,7 @@ export class LoopDaemon {
1281
1282
  log("Task timed out — releasing lease and cleaning up");
1282
1283
  this._releaseLease(task.taskId);
1283
1284
  this.running.delete(task.taskId);
1284
- this._notifyHumanReviewNeeded(
1285
+ await this._notifyHumanReviewNeeded(
1285
1286
  task,
1286
1287
  `Task timed out after ${this.config.taskTimeoutMs}ms — manual review needed`,
1287
1288
  );
@@ -1398,18 +1399,18 @@ export class LoopDaemon {
1398
1399
 
1399
1400
  // ── Notify on blocked / max-iterations ─────────────────────────────
1400
1401
  if (verdict === "blocked") {
1401
- this._notifyHumanReviewNeeded(
1402
+ await this._notifyHumanReviewNeeded(
1402
1403
  task,
1403
1404
  `Loop blocked: ${finalState.review?.summary ?? "no details"}`,
1404
1405
  );
1405
1406
  } else if (verdict === "changes_requested") {
1406
- this._notifyHumanReviewNeeded(
1407
+ await this._notifyHumanReviewNeeded(
1407
1408
  task,
1408
1409
  `${iterations} iteration(s) exhausted with changes still requested: ${finalState.review?.comments.map((c) => c.comment).join("; ") ?? "no details"}`,
1409
1410
  );
1410
1411
  } else {
1411
1412
  // approved
1412
- this._notifyReadyForClient(task);
1413
+ await this._notifyReadyForClient(task);
1413
1414
  }
1414
1415
 
1415
1416
  // ── Notify agent of loop completion (TUI todo count) ───────────────
@@ -1535,19 +1536,33 @@ export class LoopDaemon {
1535
1536
 
1536
1537
  // ─── Notifications ────────────────────────────────────────────────────
1537
1538
 
1538
- private _notifyHumanReviewNeeded(task: TriggeredTask, reason: string): void {
1539
+ private async _notifyHumanReviewNeeded(task: TriggeredTask, reason: string): Promise<void> {
1539
1540
  if (!this.notificationCenter) return;
1540
1541
  console.log(`[daemon] Notifying HumanReviewNeeded: ${reason}`);
1541
- this.notificationCenter
1542
- .notify("HumanReviewNeeded", {
1542
+ try {
1543
+ const results = await this.notificationCenter.notify("HumanReviewNeeded", {
1543
1544
  jobId: task.taskId,
1544
1545
  requirement: task.request,
1545
1546
  taskId: task.taskId,
1546
1547
  error: reason,
1547
- })
1548
- .catch((err) => {
1549
- console.warn(`[daemon] Notification failed: ${err}`);
1550
1548
  });
1549
+ for (const result of results) {
1550
+ if (!result.success) {
1551
+ // Redact sensitive data — do not log token, chat ID, or full payload
1552
+ const category = result.error?.includes("401") || result.error?.includes("403")
1553
+ ? "auth"
1554
+ : result.error?.includes("400")
1555
+ ? "bad_request"
1556
+ : result.error?.includes("429")
1557
+ ? "rate_limit"
1558
+ : "unknown";
1559
+ console.warn(`[daemon] Notification failed to ${result.channel} (${category}): ${result.error}`);
1560
+ }
1561
+ }
1562
+ }
1563
+ catch (err) {
1564
+ console.warn(`[daemon] Notification threw: ${err}`);
1565
+ }
1551
1566
  }
1552
1567
 
1553
1568
  private _closeBdIssue(taskId: string, verdict: string): void {
@@ -1571,18 +1586,31 @@ export class LoopDaemon {
1571
1586
  }
1572
1587
  }
1573
1588
 
1574
- private _notifyReadyForClient(task: TriggeredTask): void {
1589
+ private async _notifyReadyForClient(task: TriggeredTask): Promise<void> {
1575
1590
  if (!this.notificationCenter) return;
1576
1591
  console.log(`[daemon] Notifying ReadyForClient`);
1577
- this.notificationCenter
1578
- .notify("ReadyForClient", {
1592
+ try {
1593
+ const results = await this.notificationCenter.notify("ReadyForClient", {
1579
1594
  jobId: task.taskId,
1580
1595
  requirement: task.request,
1581
1596
  taskId: task.taskId,
1582
- })
1583
- .catch((err) => {
1584
- console.warn(`[daemon] Notification failed: ${err}`);
1585
1597
  });
1598
+ for (const result of results) {
1599
+ if (!result.success) {
1600
+ const category = result.error?.includes("401") || result.error?.includes("403")
1601
+ ? "auth"
1602
+ : result.error?.includes("400")
1603
+ ? "bad_request"
1604
+ : result.error?.includes("429")
1605
+ ? "rate_limit"
1606
+ : "unknown";
1607
+ console.warn(`[daemon] Notification failed to ${result.channel} (${category}): ${result.error}`);
1608
+ }
1609
+ }
1610
+ }
1611
+ catch (err) {
1612
+ console.warn(`[daemon] Notification threw: ${err}`);
1613
+ }
1586
1614
  }
1587
1615
 
1588
1616
  /**
@@ -26,6 +26,17 @@ import {
26
26
  type ReviewVerdict,
27
27
  ReviewVerdictSchema,
28
28
  } from "./agents.js";
29
+
30
+ // ─── Module-level state for Jev provider selection ──────────────────────────
31
+ let _currentProvider: "minimax" | "gpt" = "minimax";
32
+
33
+ export function setJevProvider(provider: "minimax" | "gpt"): void {
34
+ _currentProvider = provider;
35
+ }
36
+
37
+ export function getJevProvider(): "minimax" | "gpt" {
38
+ return _currentProvider;
39
+ }
29
40
  import { readFileSync } from "node:fs";
30
41
  import { WriteReviewBlackboard } from "../../packages/write-review/src/blackboard.js";
31
42
  import { getApprovedPatternStore } from "../../packages/trajectory/src/index.js";
@@ -64,8 +75,26 @@ const LoopState = Annotation.Root({
64
75
  reducer: (_prev, next) => next,
65
76
  default: () => ({}),
66
77
  }),
78
+ /** User input request - when Jev decides to ask the user.
79
+ * null = no user input needed (continue automatically)
80
+ * { type: "yesNo", question, choices? } = binary choice
81
+ * { type: "open", question, expectedFormat? } = free text response */
82
+ userInput: Annotation<UserInput | null>({
83
+ reducer: (_prev, next) => next,
84
+ default: () => null,
85
+ }),
86
+ /** User's response to the question */
87
+ userResponse: Annotation<string | null>({
88
+ reducer: (_prev, next) => next,
89
+ default: () => null,
90
+ }),
67
91
  });
68
92
 
93
+ /** User input types that Jev can request */
94
+ export type UserInput =
95
+ | { type: "yesNo"; question: string; yesLabel?: string; noLabel?: string }
96
+ | { type: "open"; question: string; expectedFormat?: string };
97
+
69
98
  export type LoopState = typeof LoopState.State;
70
99
 
71
100
  // ─── Dependencies (inject real agents or dry-run stubs) ─────────────────────
@@ -419,16 +448,35 @@ export async function buildRealLoopDeps(
419
448
  maxIterations: options.maxIterations ?? 3,
420
449
  onStep,
421
450
  plan: async (request) => {
422
- // P1-2: detect autonomy signal in the request and unlock full autonomous mode
423
- _directive = isAutonomyRequest(request) ? autonomyDirective() : "";
424
- return lastMessage(
425
- await planner.invoke({
451
+ // P1-2: detect autonomy signal in the request and unlock full autonomous mode
452
+ _directive = isAutonomyRequest(request) ? autonomyDirective() : "";
453
+ const plannerResponse = await planner.invoke({
426
454
  messages: [
427
455
  { role: "user", content: _directive + withScoreboard(request) },
428
456
  ],
429
- }),
430
- );
431
- },
457
+ });
458
+ const planOutput = lastMessage(plannerResponse) ?? "";
459
+
460
+ // Import and use Jev logger (async - logs to file + Telegram)
461
+ try {
462
+ const { logJevDecision, parseJevDecision } = await import("./jev-logger.js");
463
+ const jevDecision = parseJevDecision(planOutput);
464
+ setJevProvider(jevDecision.provider);
465
+ await logJevDecision({
466
+ timestamp: new Date().toISOString(),
467
+ request,
468
+ provider: jevDecision.provider,
469
+ reason: jevDecision.reason,
470
+ userInput: jevDecision.userInput,
471
+ escalate: false,
472
+ raw: planOutput,
473
+ });
474
+ } catch (logErr) {
475
+ console.error("[JEV] Logger error:", (logErr as Error).message);
476
+ }
477
+
478
+ return planOutput;
479
+ },
432
480
  write: async (plan, review) => {
433
481
  const userMsg = review
434
482
  ? `## Plan\n${plan}\n\n## Review comments to address\n${review.comments