@curie-agent/core 0.2.4 → 0.3.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 (111) hide show
  1. package/LICENSE +179 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/channel-router.js +1 -1
  4. package/dist/src/channel-router.js.map +1 -1
  5. package/dist/src/context.d.ts +11 -0
  6. package/dist/src/context.d.ts.map +1 -0
  7. package/dist/src/context.js +80 -0
  8. package/dist/src/context.js.map +1 -0
  9. package/dist/src/cron-manager.d.ts +27 -8
  10. package/dist/src/cron-manager.d.ts.map +1 -1
  11. package/dist/src/cron-manager.js +121 -33
  12. package/dist/src/cron-manager.js.map +1 -1
  13. package/dist/src/event-bus.d.ts +71 -0
  14. package/dist/src/event-bus.d.ts.map +1 -1
  15. package/dist/src/event-bus.js.map +1 -1
  16. package/dist/src/heartbeat-delivery.d.ts +2 -0
  17. package/dist/src/heartbeat-delivery.d.ts.map +1 -1
  18. package/dist/src/heartbeat-delivery.js +20 -3
  19. package/dist/src/heartbeat-delivery.js.map +1 -1
  20. package/dist/src/heartbeat-executor.d.ts +36 -2
  21. package/dist/src/heartbeat-executor.d.ts.map +1 -1
  22. package/dist/src/heartbeat-executor.js +32 -40
  23. package/dist/src/heartbeat-executor.js.map +1 -1
  24. package/dist/src/identity-files.d.ts +29 -0
  25. package/dist/src/identity-files.d.ts.map +1 -0
  26. package/dist/src/identity-files.js +59 -0
  27. package/dist/src/identity-files.js.map +1 -0
  28. package/dist/src/index.d.ts +19 -4
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +13 -2
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/pricing.d.ts +29 -0
  33. package/dist/src/pricing.d.ts.map +1 -0
  34. package/dist/src/pricing.js +85 -0
  35. package/dist/src/pricing.js.map +1 -0
  36. package/dist/src/safety/command-guard.d.ts.map +1 -1
  37. package/dist/src/safety/command-guard.js +35 -1
  38. package/dist/src/safety/command-guard.js.map +1 -1
  39. package/dist/src/safety/path-guard.d.ts +7 -3
  40. package/dist/src/safety/path-guard.d.ts.map +1 -1
  41. package/dist/src/safety/path-guard.js +14 -5
  42. package/dist/src/safety/path-guard.js.map +1 -1
  43. package/dist/src/safety/snapshot.js +1 -1
  44. package/dist/src/safety/snapshot.js.map +1 -1
  45. package/dist/src/session-store.d.ts +3 -1
  46. package/dist/src/session-store.d.ts.map +1 -1
  47. package/dist/src/session-store.js +24 -1
  48. package/dist/src/session-store.js.map +1 -1
  49. package/dist/src/settings.d.ts +98 -1
  50. package/dist/src/settings.d.ts.map +1 -1
  51. package/dist/src/settings.js +363 -83
  52. package/dist/src/settings.js.map +1 -1
  53. package/dist/src/shell-detect.d.ts +12 -0
  54. package/dist/src/shell-detect.d.ts.map +1 -0
  55. package/dist/src/shell-detect.js +31 -0
  56. package/dist/src/shell-detect.js.map +1 -0
  57. package/dist/src/subagent-executor.d.ts +68 -0
  58. package/dist/src/subagent-executor.d.ts.map +1 -0
  59. package/dist/src/subagent-executor.js +289 -0
  60. package/dist/src/subagent-executor.js.map +1 -0
  61. package/dist/src/task-executor.d.ts +31 -0
  62. package/dist/src/task-executor.d.ts.map +1 -0
  63. package/dist/src/task-executor.js +91 -0
  64. package/dist/src/task-executor.js.map +1 -0
  65. package/dist/src/task-manager.d.ts +83 -0
  66. package/dist/src/task-manager.d.ts.map +1 -0
  67. package/dist/src/task-manager.js +308 -0
  68. package/dist/src/task-manager.js.map +1 -0
  69. package/dist/src/task-migration.d.ts +10 -0
  70. package/dist/src/task-migration.d.ts.map +1 -0
  71. package/dist/src/task-migration.js +144 -0
  72. package/dist/src/task-migration.js.map +1 -0
  73. package/dist/src/task-summary.d.ts +7 -0
  74. package/dist/src/task-summary.d.ts.map +1 -0
  75. package/dist/src/task-summary.js +34 -0
  76. package/dist/src/task-summary.js.map +1 -0
  77. package/dist/src/template-utils.d.ts +16 -0
  78. package/dist/src/template-utils.d.ts.map +1 -0
  79. package/dist/src/template-utils.js +72 -0
  80. package/dist/src/template-utils.js.map +1 -0
  81. package/dist/src/token-monitor.d.ts +50 -0
  82. package/dist/src/token-monitor.d.ts.map +1 -0
  83. package/dist/src/token-monitor.js +119 -0
  84. package/dist/src/token-monitor.js.map +1 -0
  85. package/dist/src/turn-loop.d.ts +10 -1
  86. package/dist/src/turn-loop.d.ts.map +1 -1
  87. package/dist/src/turn-loop.js +152 -23
  88. package/dist/src/turn-loop.js.map +1 -1
  89. package/dist/src/unified-task.d.ts +76 -0
  90. package/dist/src/unified-task.d.ts.map +1 -0
  91. package/dist/src/unified-task.js +196 -0
  92. package/dist/src/unified-task.js.map +1 -0
  93. package/package.json +16 -11
  94. package/templates/AGENTS.md +53 -0
  95. package/templates/HEARTBEAT.md +37 -0
  96. package/templates/MEMORY.md +29 -0
  97. package/templates/SOUL.md +28 -0
  98. package/templates/TOOLS.md +29 -0
  99. package/templates/USER.md +35 -0
  100. package/templates/WIKI.md +146 -0
  101. package/templates/settings.json +90 -0
  102. package/templates/skills/deep-research/SKILL.md +273 -0
  103. package/templates/skills/deep-research/references/report-structure.md +149 -0
  104. package/templates/skills/deep-research/references/topic-guides.md +151 -0
  105. package/templates/skills/planning/SKILL.md +222 -0
  106. package/templates/skills/planning/references/business.md +82 -0
  107. package/templates/skills/planning/references/creative.md +111 -0
  108. package/templates/skills/planning/references/events.md +109 -0
  109. package/templates/skills/planning/references/personal.md +107 -0
  110. package/templates/skills/planning/references/templates.md +279 -0
  111. package/templates/skills/wiki/SKILL.md +70 -0
@@ -0,0 +1,50 @@
1
+ import { type PriceTier } from './pricing.js';
2
+ export { estimateCost, PriceTier, parseTieredPricing, selectTier } from './pricing.js';
3
+ export type TokenEvent = {
4
+ type: 'context-warning';
5
+ fillPct: number;
6
+ message: string;
7
+ } | {
8
+ type: 'context-compaction-needed';
9
+ fillPct: number;
10
+ message: string;
11
+ } | {
12
+ type: 'context-forced-compaction';
13
+ fillPct: number;
14
+ message: string;
15
+ } | {
16
+ type: 'tier-warning';
17
+ threshold: number;
18
+ oldTier: PriceTier;
19
+ newTier: PriceTier;
20
+ message: string;
21
+ };
22
+ export interface TokenMonitorConfig {
23
+ contextWindowSize: number;
24
+ thresholdPct: number;
25
+ warnThresholdPct: number;
26
+ forcedThresholdPct: number;
27
+ pricingTierWarn: boolean;
28
+ /** Model name for cost estimation (e.g., 'claude-sonnet-4-6'). */
29
+ model?: string;
30
+ }
31
+ export declare class TokenMonitor {
32
+ config: TokenMonitorConfig;
33
+ cumulativeInputTokens: number;
34
+ lastTrackedTokens: number;
35
+ private acknowledged;
36
+ constructor(config: TokenMonitorConfig);
37
+ getFillPct(): number;
38
+ getStatus(): 'ok' | 'warning' | 'compaction-needed' | 'forced-compaction';
39
+ private parseTiers;
40
+ private selectTier;
41
+ checkTierCrossing(tiersRaw?: PriceTier[]): TokenEvent | null;
42
+ addTokens(inputTokens: number, outputTokens: number, tiersRaw?: PriceTier[]): TokenEvent[];
43
+ acknowledge(eventType: string): void;
44
+ reset(): void;
45
+ setContextWindowSize(size: number): void;
46
+ setThresholdPct(pct: number): void;
47
+ setWarnThresholdPct(pct: number): void;
48
+ setForcedThresholdPct(pct: number): void;
49
+ }
50
+ //# sourceMappingURL=token-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-monitor.d.ts","sourceRoot":"","sources":["../../src/token-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvF,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,2BAA2B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,2BAA2B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzG,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,YAAY;IACvB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,YAAY,CAAqB;gBAE7B,MAAM,EAAE,kBAAkB;IAMtC,UAAU,IAAI,MAAM;IAKpB,SAAS,IAAI,IAAI,GAAG,SAAS,GAAG,mBAAmB,GAAG,mBAAmB;IAQzE,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,UAAU;IAkBlB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,UAAU,GAAG,IAAI;IA0B5D,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE;IA0B1F,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIpC,KAAK,IAAI,IAAI;IAMb,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIlC,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAItC,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAGzC"}
@@ -0,0 +1,119 @@
1
+ import { estimateCost } from './pricing.js';
2
+ export { estimateCost, parseTieredPricing, selectTier } from './pricing.js';
3
+ export class TokenMonitor {
4
+ config;
5
+ cumulativeInputTokens;
6
+ lastTrackedTokens;
7
+ acknowledged = new Set();
8
+ constructor(config) {
9
+ this.config = config;
10
+ this.cumulativeInputTokens = 0;
11
+ this.lastTrackedTokens = 0;
12
+ }
13
+ getFillPct() {
14
+ if (this.config.contextWindowSize <= 0)
15
+ return 0;
16
+ return Math.min(100, Math.round((this.cumulativeInputTokens / this.config.contextWindowSize) * 100));
17
+ }
18
+ getStatus() {
19
+ const pct = this.getFillPct();
20
+ if (pct >= this.config.forcedThresholdPct)
21
+ return 'forced-compaction';
22
+ if (pct >= this.config.thresholdPct)
23
+ return 'compaction-needed';
24
+ if (pct >= this.config.warnThresholdPct)
25
+ return 'warning';
26
+ return 'ok';
27
+ }
28
+ parseTiers(tiersRaw) {
29
+ if (!tiersRaw || tiersRaw.length === 0)
30
+ return [];
31
+ return tiersRaw;
32
+ }
33
+ selectTier(tiers, totalTokens) {
34
+ let result = tiers[0] ?? { in: 0, out: 0 };
35
+ for (const tier of tiers) {
36
+ if (tier.threshold !== undefined && totalTokens >= tier.threshold) {
37
+ result = tier;
38
+ }
39
+ }
40
+ // Find the tier just below the current one
41
+ let prev = tiers[0] ?? { in: 0, out: 0 };
42
+ for (let i = 0; i < tiers.length; i++) {
43
+ if (tiers[i] === result) {
44
+ prev = i > 0 ? tiers[i - 1] : tiers[0];
45
+ break;
46
+ }
47
+ }
48
+ return [prev, result];
49
+ }
50
+ checkTierCrossing(tiersRaw) {
51
+ if (!this.config.pricingTierWarn)
52
+ return null;
53
+ const tiers = this.parseTiers(tiersRaw);
54
+ if (tiers.length < 2)
55
+ return null;
56
+ const [prevTier, newTier] = this.selectTier(tiers, this.cumulativeInputTokens);
57
+ if (prevTier.in === newTier.in)
58
+ return null;
59
+ const threshold = newTier.threshold ?? 0;
60
+ const key = `tier-${threshold}`;
61
+ if (this.acknowledged.has(key))
62
+ return null;
63
+ const oldRate = `$${prevTier.in.toFixed(2)}/$${prevTier.out.toFixed(2)} per 1M tokens`;
64
+ const newRate = `$${newTier.in.toFixed(2)}/$${newTier.out.toFixed(2)} per 1M tokens`;
65
+ const cost = estimateCost(this.config.model || 'claude-sonnet-4-6', this.cumulativeInputTokens, 0);
66
+ const oldCost = estimateCost(this.config.model || 'claude-sonnet-4-6', this.cumulativeInputTokens, 0, undefined);
67
+ return {
68
+ type: 'tier-warning',
69
+ threshold,
70
+ oldTier: prevTier,
71
+ newTier,
72
+ message: `⚠ Entering ${Math.round(threshold / 1000)}k+ tier — ${oldRate} -> ${newRate} (current session cost: $${cost.toFixed(2)})`,
73
+ };
74
+ }
75
+ addTokens(inputTokens, outputTokens, tiersRaw) {
76
+ this.cumulativeInputTokens += inputTokens;
77
+ const events = [];
78
+ const status = this.getStatus();
79
+ const pct = this.getFillPct();
80
+ if (status === 'warning') {
81
+ events.push({ type: 'context-warning', fillPct: pct, message: `Context window ${pct}% full (warning threshold: ${this.config.warnThresholdPct}%)` });
82
+ }
83
+ else if (status === 'compaction-needed') {
84
+ events.push({ type: 'context-compaction-needed', fillPct: pct, message: `Context window ${pct}% full (compaction threshold: ${this.config.thresholdPct}%)` });
85
+ }
86
+ else if (status === 'forced-compaction') {
87
+ events.push({ type: 'context-forced-compaction', fillPct: pct, message: `Context window ${pct}% full (forced compaction at ${this.config.forcedThresholdPct}%)` });
88
+ }
89
+ const tierEvent = this.checkTierCrossing(tiersRaw);
90
+ if (tierEvent) {
91
+ events.push(tierEvent);
92
+ }
93
+ if (events.length > 0) {
94
+ this.lastTrackedTokens = this.cumulativeInputTokens;
95
+ }
96
+ return events;
97
+ }
98
+ acknowledge(eventType) {
99
+ this.acknowledged.add(eventType);
100
+ }
101
+ reset() {
102
+ this.cumulativeInputTokens = 0;
103
+ this.lastTrackedTokens = 0;
104
+ this.acknowledged.clear();
105
+ }
106
+ setContextWindowSize(size) {
107
+ this.config.contextWindowSize = size;
108
+ }
109
+ setThresholdPct(pct) {
110
+ this.config.thresholdPct = pct;
111
+ }
112
+ setWarnThresholdPct(pct) {
113
+ this.config.warnThresholdPct = pct;
114
+ }
115
+ setForcedThresholdPct(pct) {
116
+ this.config.forcedThresholdPct = pct;
117
+ }
118
+ }
119
+ //# sourceMappingURL=token-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-monitor.js","sourceRoot":"","sources":["../../src/token-monitor.ts"],"names":[],"mappings":"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"}
@@ -23,11 +23,13 @@ export interface ProviderStream {
23
23
  system?: string;
24
24
  signal?: AbortSignal;
25
25
  effort?: ReasoningEffort;
26
+ maxTokens?: number;
26
27
  }): CancelableIterable<ProviderEvent>;
27
28
  /** Non-streaming call — used for quick evaluations (e.g. harm-check). */
28
29
  check(prompt: string, args?: {
29
30
  model?: string;
30
31
  system?: string;
32
+ signal?: AbortSignal;
31
33
  }): Promise<string>;
32
34
  }
33
35
  export interface Tool {
@@ -36,7 +38,7 @@ export interface Tool {
36
38
  description: string;
37
39
  inputSchema: unknown;
38
40
  };
39
- execute: (input: Record<string, unknown>, settings: any, cwd?: string) => Promise<{
41
+ execute: (input: Record<string, unknown>, settings: any, cwd?: string, sessionId?: string) => Promise<{
40
42
  output: unknown;
41
43
  error?: string;
42
44
  }>;
@@ -44,6 +46,9 @@ export interface Tool {
44
46
  export type ProviderEvent = {
45
47
  type: 'text-delta';
46
48
  text: string;
49
+ } | {
50
+ type: 'thinking-delta';
51
+ text: string;
47
52
  } | {
48
53
  type: 'thinking-block';
49
54
  thinking: string;
@@ -66,6 +71,7 @@ export type ProviderEvent = {
66
71
  } | {
67
72
  type: 'stop';
68
73
  reason: string;
74
+ errorDetail?: string;
69
75
  };
70
76
  export interface TurnLoopConfig {
71
77
  provider: ProviderStream;
@@ -86,10 +92,13 @@ export interface TurnLoopConfig {
86
92
  resumeSessionId?: string;
87
93
  /** Called when a tool call needs interactive approval. Return true to allow, false to deny. */
88
94
  onApprovalAsk?: (req: {
95
+ toolCallId?: string;
89
96
  name: string;
90
97
  input: Record<string, unknown>;
91
98
  reason: string;
92
99
  }) => Promise<boolean>;
100
+ /** Optional session type / entrypoint (e.g. webui, tui, telegram, heartbeat) */
101
+ type?: string;
93
102
  }
94
103
  export interface TurnLoopResult {
95
104
  events: Event[];
@@ -1 +1 @@
1
- {"version":3,"file":"turn-loop.d.ts","sourceRoot":"","sources":["../../src/turn-loop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"turn-loop.d.ts","sourceRoot":"","sources":["../../src/turn-loop.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,19 @@
1
+ import fs from 'node:fs';
1
2
  import { EventBus } from './event-bus.js';
2
3
  import { SessionStore } from './session-store.js';
3
4
  import { PermissionEngine } from './permission.js';
4
5
  import { createSnapshot } from './safety/snapshot.js';
6
+ import { withDateContext } from './context.js';
5
7
  // The provider's stream() returns a CancelableIterable with a cancel() method
6
8
  // that synchronously aborts the underlying HTTP connection (e.g., Anthropic's
7
9
  // stream.abort()). The TurnLoop wires this cancel() to the AbortSignal's abort
8
10
  // event so pressing Esc closes the stream immediately instead of waiting for
9
11
  // the next SSE event from the server.
12
+ // Brief delay to let WebSocket events flush to clients between approval requests.
13
+ const approvalThrottleMs = 50;
14
+ function sleep(ms) {
15
+ return new Promise((resolve) => setTimeout(resolve, ms));
16
+ }
10
17
  export class TurnLoop {
11
18
  bus;
12
19
  store;
@@ -45,19 +52,36 @@ export class TurnLoop {
45
52
  this._cwd = newCwd;
46
53
  }
47
54
  async evaluateHarm(toolName, input) {
55
+ // Local/Ollama providers run on a single-threaded server. Firing check()
56
+ // re-entrantly while the main stream is in-flight serializes both requests
57
+ // and causes llama.cpp to appear hung. Thinking models (qwen3.x, deepseek-r1,
58
+ // etc.) also never emit a clean APPROVE/DENY token, so the harm-check always
59
+ // times out against these models anyway.
60
+ if (['local', 'ollama'].includes(this.config.provider.name)) {
61
+ return { approved: true, reason: 'local provider: LLM harm-check skipped' };
62
+ }
48
63
  const inputJson = JSON.stringify(input);
49
64
  const systemPrompt = 'You are a safety evaluator. Given a tool call, determine if it is potentially harmful to the user\'s system. Harmful means: deleting files, executing arbitrary shell commands, sending data to external services, modifying system configuration, or any action that could cause data loss or security compromise. Your response must be exactly one of these two strings: APPROVE or DENY. Do not add any other text.';
50
65
  const prompt = `Evaluate this tool call:\nTool: ${toolName}\nInput: ${inputJson}`;
51
66
  try {
52
- const response = await this.config.provider.check(prompt, { model: this.config.model, system: systemPrompt });
53
- const decision = response.trim().toUpperCase();
54
- if (decision === 'APPROVE') {
55
- return { approved: true, reason: `LLM harm-check: APPROVE` };
67
+ // Use an AbortController so the HTTP request itself is cancelled on timeout,
68
+ // not just the Promise.race outcome — prevents zombie requests from queuing.
69
+ const abortCtrl = new AbortController();
70
+ const timeoutId = setTimeout(() => abortCtrl.abort(new Error('harm-check timeout')), 15_000);
71
+ try {
72
+ const response = await this.config.provider.check(prompt, { model: this.config.model, system: systemPrompt, signal: abortCtrl.signal });
73
+ const decision = response.trim().toUpperCase();
74
+ if (decision === 'APPROVE') {
75
+ return { approved: true, reason: `LLM harm-check: APPROVE` };
76
+ }
77
+ return { approved: false, reason: `LLM harm-check: DENY — ${response.trim()}` };
78
+ }
79
+ finally {
80
+ clearTimeout(timeoutId);
56
81
  }
57
- return { approved: false, reason: `LLM harm-check: DENY — ${response.trim()}` };
58
82
  }
59
83
  catch {
60
- // If LLM check fails, deny for safety
84
+ // If LLM check fails or times out, deny for safety
61
85
  return { approved: false, reason: 'LLM harm-check failed — denied' };
62
86
  }
63
87
  }
@@ -65,6 +89,7 @@ export class TurnLoop {
65
89
  const messages = [];
66
90
  let currentTurn = {
67
91
  assistantDeltas: [],
92
+ thinkingDeltas: [],
68
93
  toolCalls: [],
69
94
  toolResults: [],
70
95
  };
@@ -75,7 +100,7 @@ export class TurnLoop {
75
100
  messages.push(...this.buildTurnMessages(currentTurn));
76
101
  }
77
102
  messages.push({ role: 'user', content: event.text });
78
- currentTurn = { assistantDeltas: [], toolCalls: [], toolResults: [] };
103
+ currentTurn = { assistantDeltas: [], thinkingDeltas: [], toolCalls: [], toolResults: [] };
79
104
  hasActiveTurn = true;
80
105
  continue;
81
106
  }
@@ -84,6 +109,9 @@ export class TurnLoop {
84
109
  if (event.type === 'assistant-delta') {
85
110
  currentTurn.assistantDeltas.push(event.text);
86
111
  }
112
+ else if (event.type === 'thinking-delta') {
113
+ currentTurn.thinkingDeltas.push(event.text);
114
+ }
87
115
  else if (event.type === 'tool-call') {
88
116
  currentTurn.toolCalls.push({ toolCallId: event.toolCallId, name: event.name, input: event.input, thoughtSignature: event.thoughtSignature });
89
117
  }
@@ -98,8 +126,11 @@ export class TurnLoop {
98
126
  }
99
127
  buildTurnMessages(turn) {
100
128
  const msgs = [];
101
- // Assistant message: text blocks + tool-use blocks
129
+ // Assistant message: thinking blocks + text blocks + tool-use blocks
102
130
  const assistantBlocks = [];
131
+ for (const text of turn.thinkingDeltas) {
132
+ assistantBlocks.push({ type: 'thinking', thinking: text, signature: '' });
133
+ }
103
134
  for (const text of turn.assistantDeltas) {
104
135
  assistantBlocks.push({ type: 'text', text });
105
136
  }
@@ -132,16 +163,23 @@ export class TurnLoop {
132
163
  const loaded = this.store.load(effectiveSessionId);
133
164
  if (loaded) {
134
165
  session = loaded;
166
+ if (this.config.type && !session.type) {
167
+ session.type = this.config.type;
168
+ try {
169
+ fs.writeFileSync(this.store.metadataPath(session.id), JSON.stringify(session, null, 2) + '\n');
170
+ }
171
+ catch { }
172
+ }
135
173
  }
136
174
  else {
137
175
  // Session ID was provided but doesn't exist on disk yet
138
176
  // (e.g. ChannelRouter generates a placeholder before TurnLoop creates it)
139
- session = this.store.create(this.config.cwd, this.config.model, this.config.provider.name);
177
+ session = this.store.create(this.config.cwd, this.config.model, this.config.provider.name, this.config.type);
140
178
  this.activeSessionId = session.id;
141
179
  }
142
180
  }
143
181
  else {
144
- session = this.store.create(this.config.cwd, this.config.model, this.config.provider.name);
182
+ session = this.store.create(this.config.cwd, this.config.model, this.config.provider.name, this.config.type);
145
183
  }
146
184
  this.activeSessionId ??= session.id;
147
185
  // Resume: load stored events and reconstruct messages array
@@ -166,16 +204,22 @@ export class TurnLoop {
166
204
  this.messages.push({ role: 'user', content: prompt });
167
205
  let turn = 0;
168
206
  const maxTurns = this.config.maxTurns ?? 50;
207
+ let continuationCount = 0;
208
+ const maxContinuations = 5;
169
209
  try {
170
210
  while (turn < maxTurns && !this.abort) {
171
211
  turn++;
212
+ if (process.env.DEBUG_PROVIDER) {
213
+ console.log(`[turn-loop] turn=${turn}, messages.length=${this.messages.length}`);
214
+ }
172
215
  // Create git snapshot before each turn (best-effort).
173
- if (this.config.settings.SAFETY_SNAPSHOTS !== 'off') {
216
+ if (this.config.settings.safety?.snapshots !== 'off') {
174
217
  await createSnapshot(this._cwd, prompt).catch(() => {
175
218
  // Silently ignore — snapshots are recovery, not enforcement
176
219
  });
177
220
  }
178
221
  let fullText = '';
222
+ let hitLengthLimit = false;
179
223
  const toolCalls = [];
180
224
  let totalToolCalls = 0;
181
225
  let websearchToolCalls = 0;
@@ -183,7 +227,7 @@ export class TurnLoop {
183
227
  // Local providers (Ollama, llama.cpp) don't support Anthropic thinking blocks.
184
228
  // Strip thinking blocks from message history to avoid serialization errors.
185
229
  const self = this;
186
- const isLocal = self.config.provider.name === 'local';
230
+ const isLocal = ['local', 'ollama'].includes(self.config.provider.name);
187
231
  const streamMessages = isLocal
188
232
  ? self.messages.map((m) => {
189
233
  if (m.role === 'assistant' && Array.isArray(m.content)) {
@@ -194,16 +238,48 @@ export class TurnLoop {
194
238
  : self.messages;
195
239
  // Capture abortController before the loop — it's always set by this point.
196
240
  const abortCtrl = self.abortController;
241
+ // Pre-flight: abort before sending if estimated payload exceeds context window.
242
+ // Count actual text content chars (not JSON structure) and divide by 4 (~4 chars/token).
243
+ {
244
+ const ctxWindow = self.config.settings.providers[self.config.settings.current_provider]?.model_context_window ?? 131072;
245
+ const reservedForOutput = self.config.settings.providers[self.config.settings.current_provider]?.max_output_tokens ?? 65536;
246
+ let contentChars = (self.config.system?.length ?? 0) + 200; // system prompt + date context overhead
247
+ for (const m of streamMessages) {
248
+ if (typeof m.content === 'string') {
249
+ contentChars += m.content.length;
250
+ }
251
+ else if (Array.isArray(m.content)) {
252
+ for (const b of m.content) {
253
+ if (b.text)
254
+ contentChars += b.text.length;
255
+ else if (b.thinking)
256
+ contentChars += b.thinking.length;
257
+ else if (b.input)
258
+ contentChars += JSON.stringify(b.input).length;
259
+ }
260
+ }
261
+ }
262
+ const estimatedInputTokens = Math.ceil(contentChars / 4);
263
+ if (estimatedInputTokens > ctxWindow - reservedForOutput) {
264
+ emit({ type: 'error', id: session.id, timestamp: Date.now(), message: `Estimated input (~${Math.round(estimatedInputTokens / 1000)}k tokens) would exceed the context window ` +
265
+ `(${Math.round(ctxWindow / 1000)}k) for provider "${self.config.settings.current_provider}". ` +
266
+ `Run /context compact to summarize history, or switch to a model with a larger context window.` });
267
+ emit({ type: 'session-stop', id: session.id, reason: 'error', timestamp: Date.now() });
268
+ return { events: self.bus.history(), sessionId: session.id, reason: 'error' };
269
+ }
270
+ }
197
271
  // Start the provider stream and capture its cancel function.
198
272
  // The cancel() method synchronously aborts the underlying HTTP connection
199
273
  // (e.g., Anthropic's stream.abort()), making Esc feel instantaneous.
274
+ const maxTokens = self.config.settings.providers[self.config.settings.current_provider]?.max_output_tokens;
200
275
  const cancelable = self.config.provider.stream({
201
276
  messages: streamMessages,
202
277
  tools: self.config.tools.map((t) => t.definition),
203
278
  model: self.config.model,
204
- system: self.config.system,
279
+ system: withDateContext(self.config.system),
205
280
  signal: abortCtrl.signal,
206
281
  effort: self.config.effort,
282
+ maxTokens,
207
283
  });
208
284
  // Wire the AbortSignal's abort event to cancel the stream immediately.
209
285
  // This is what makes Esc feel instant — it calls stream.abort() on the
@@ -220,6 +296,9 @@ export class TurnLoop {
220
296
  fullText += event.text;
221
297
  emit({ type: 'assistant-delta', id: session.id, text: event.text, timestamp: Date.now() });
222
298
  break;
299
+ case 'thinking-delta':
300
+ emit({ type: 'thinking-delta', id: session.id, text: event.text, timestamp: Date.now() });
301
+ break;
223
302
  case 'thinking-block':
224
303
  thinkingBlocks.push({ thinking: event.thinking, signature: event.signature });
225
304
  break;
@@ -230,6 +309,27 @@ export class TurnLoop {
230
309
  case 'usage':
231
310
  emit({ type: 'usage', id: session.id, inputTokens: event.inputTokens, outputTokens: event.outputTokens, cacheReadTokens: event.cacheReadTokens, cacheWriteTokens: event.cacheWriteTokens, timestamp: Date.now() });
232
311
  break;
312
+ case 'stop':
313
+ if (event.reason === 'error' && event.errorDetail) {
314
+ const isCtxExceeded = /exceeds.*context|context.*size|too many tokens/i.test(event.errorDetail);
315
+ emit({ type: 'error', id: session.id, timestamp: Date.now(), message: isCtxExceeded
316
+ ? `Context window exceeded: ${event.errorDetail}. Run /context compact to summarize history, or switch to a model with a larger context window.`
317
+ : `Provider stream error: ${event.errorDetail}. This can happen with incompatible local model endpoints or network issues.`
318
+ });
319
+ }
320
+ else if (event.reason === 'aborted' && !self.abort) {
321
+ // Provider timeout (not user-initiated) — surface it
322
+ emit({ type: 'status', id: session.id, message: 'Provider request timed out. The local model may be too slow or overloaded.',
323
+ timestamp: Date.now(),
324
+ });
325
+ }
326
+ else if (event.reason === 'length') {
327
+ hitLengthLimit = true;
328
+ emit({ type: 'status', id: session.id, message: 'Model hit output token limit. Continuing...',
329
+ timestamp: Date.now(),
330
+ });
331
+ }
332
+ break;
233
333
  }
234
334
  }
235
335
  }
@@ -257,6 +357,15 @@ export class TurnLoop {
257
357
  emit({ type: 'session-stop', id: session.id, reason: 'error', timestamp: Date.now() });
258
358
  return { events: self.bus.history(), sessionId: session.id, reason: 'error' };
259
359
  }
360
+ // Connection errors: server unreachable, connection reset, refused
361
+ const connErrors = ['ECONNREFUSED', 'ECONNRESET', 'ECONNABORTED', 'ENOTFOUND', 'fetch failed'];
362
+ if (connErrors.some(e => msg.includes(e))) {
363
+ emit({ type: 'error', id: session.id, message: `Provider connection error: ${msg}. Check that the local model server is running and reachable at the configured URL.`,
364
+ timestamp: Date.now(),
365
+ });
366
+ emit({ type: 'session-stop', id: session.id, reason: 'error', timestamp: Date.now() });
367
+ return { events: self.bus.history(), sessionId: session.id, reason: 'error' };
368
+ }
260
369
  throw err;
261
370
  }
262
371
  }
@@ -265,6 +374,9 @@ export class TurnLoop {
265
374
  emit({ type: 'session-stop', id: session.id, reason: 'cancelled', timestamp: Date.now() });
266
375
  return { events: this.bus.history(), sessionId: session.id, reason: 'cancelled' };
267
376
  }
377
+ if (process.env.DEBUG_PROVIDER) {
378
+ console.log(`[turn-loop] turn=${turn} stream done: textLen=${fullText.length}, toolCalls=${toolCalls.length}, thinkingBlocks=${thinkingBlocks.length}`);
379
+ }
268
380
  // Build assistant message: thinking blocks MUST come first (Anthropic
269
381
  // requires them preserved when extended thinking + tool use is combined),
270
382
  // then text, then tool-use blocks.
@@ -280,7 +392,23 @@ export class TurnLoop {
280
392
  if (assistantContent.length > 0) {
281
393
  this.messages.push({ role: 'assistant', content: assistantContent });
282
394
  }
395
+ // Hit output token limit without tool calls — tell model to continue
396
+ if (hitLengthLimit && toolCalls.length === 0) {
397
+ if (continuationCount >= maxContinuations) {
398
+ emit({ type: 'status', id: session.id, message: `Output length limit reached. Model could not complete response after ${maxContinuations} continuation attempts.`,
399
+ timestamp: Date.now() });
400
+ }
401
+ else {
402
+ continuationCount++;
403
+ this.messages.push({ role: 'user', content: '[continue — your previous response was cut off by the output token limit. If you were about to call Write with a large content parameter, use the skeleton+Edit approach: first Write a skeleton file with section headers and [PLACEHOLDER] markers, then fill each section with a separate Edit call.]' });
404
+ continue;
405
+ }
406
+ }
283
407
  if (toolCalls.length === 0) {
408
+ if (!fullText && thinkingBlocks.length === 0) {
409
+ emit({ type: 'status', id: session.id, message: 'The model did not return a response. This can happen with incompatible models or configuration issues.',
410
+ timestamp: Date.now() });
411
+ }
284
412
  emit({ type: 'assistant-stop', id: session.id, timestamp: Date.now() });
285
413
  emit({ type: 'session-stop', id: session.id, reason: 'stop', timestamp: Date.now() });
286
414
  return { events: this.bus.history(), sessionId: session.id, reason: 'stop' };
@@ -289,8 +417,8 @@ export class TurnLoop {
289
417
  if (this.abort)
290
418
  break;
291
419
  // --- Tool usage limits ---
292
- const totalLimit = this.config.settings.TOOLS_PER_CALL ?? 10;
293
- const websearchLimit = this.config.settings.WEBSEARCH_PER_CALL ?? 5;
420
+ const totalLimit = this.config.settings.tools_per_call ?? 10;
421
+ const websearchLimit = this.config.settings.websearch_per_call ?? 5;
294
422
  if (totalToolCalls >= totalLimit) {
295
423
  const msg = `Tool call limit reached (${totalLimit} per turn). Skipping remaining tool call(s).`;
296
424
  emit({ type: 'status', id: session.id, message: msg, timestamp: Date.now() });
@@ -318,8 +446,11 @@ export class TurnLoop {
318
446
  name: tc.name,
319
447
  input: tc.input,
320
448
  decision: permResult.decision,
449
+ mode: this.permission.mode,
321
450
  timestamp: Date.now(),
322
451
  });
452
+ // Yield briefly so WebSocket events flush to clients before processing the next tool.
453
+ await sleep(approvalThrottleMs);
323
454
  if (permResult.decision === 'deny') {
324
455
  emit({ type: 'approval-decision', id: session.id, toolCallId: tc.id, decision: 'deny', timestamp: Date.now() });
325
456
  this.messages.push({ role: 'tool', toolUseId: tc.id, content: `Tool call denied: ${permResult.reason}` });
@@ -341,13 +472,13 @@ export class TurnLoop {
341
472
  }
342
473
  else {
343
474
  // LLM denied — ask user for final decision
344
- approved = await ask({ name: tc.name, input: tc.input, reason: harm.reason });
475
+ approved = await ask({ toolCallId: tc.id, name: tc.name, input: tc.input, reason: harm.reason });
345
476
  decisionBy = 'user';
346
477
  }
347
478
  }
348
479
  else {
349
480
  approved = ask
350
- ? await ask({ name: tc.name, input: tc.input, reason: permResult.reason })
481
+ ? await ask({ toolCallId: tc.id, name: tc.name, input: tc.input, reason: permResult.reason })
351
482
  : true; // no callback wired — treat as allow to preserve old behavior
352
483
  decisionBy = approved ? 'user' : 'user';
353
484
  }
@@ -376,17 +507,15 @@ export class TurnLoop {
376
507
  continue;
377
508
  }
378
509
  try {
379
- const result = await tool.execute(tc.input, this.config.settings, this.config.cwd);
510
+ const result = await tool.execute(tc.input, this.config.settings, this.config.cwd, this.config.sessionId);
380
511
  emit({ type: 'tool-result', id: session.id, toolCallId: tc.id, output: result.output, error: result.error, timestamp: Date.now() });
381
- const outputStr = result.error ? `Error: ${result.error}` : JSON.stringify(result.output);
382
- const decisionLabel = toolDecisionBy === 'llm' ? 'LLM-approved' : toolDecisionBy === 'user' ? 'Approved by user' : 'Auto Approved';
383
- this.messages.push({ role: 'tool', toolUseId: tc.id, content: `${tc.name}: ${decisionLabel} — ${outputStr}` });
512
+ const outputStr = result.error ? `Error: ${result.error}` : (typeof result.output === 'string' ? result.output : JSON.stringify(result.output));
513
+ this.messages.push({ role: 'tool', toolUseId: tc.id, content: outputStr });
384
514
  }
385
515
  catch (err) {
386
516
  const msg = err instanceof Error ? err.message : String(err);
387
517
  emit({ type: 'tool-result', id: session.id, toolCallId: tc.id, output: null, error: msg, timestamp: Date.now() });
388
- const decisionLabel = toolDecisionBy === 'llm' ? 'LLM-approved' : toolDecisionBy === 'user' ? 'Approved by user' : 'Auto Approved';
389
- this.messages.push({ role: 'tool', toolUseId: tc.id, content: `${tc.name}: ${decisionLabel} — Error: ${msg}` });
518
+ this.messages.push({ role: 'tool', toolUseId: tc.id, content: `Error: ${msg}` });
390
519
  }
391
520
  }
392
521
  }