@slock-ai/daemon 0.44.2 → 0.46.0-play.20260508161002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-bridge.js +26 -109
- package/dist/{chunk-NM2MFLQ7.js → chunk-MEY33LAG.js} +1935 -285
- package/dist/{chunk-JG7ONJZ6.js → chunk-Z3PCMYZO.js} +101 -1
- package/dist/cli/index.js +67 -16
- package/dist/core.js +2 -2
- package/dist/index.js +3 -3
- package/package.json +1 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
2
3
|
buildWebSocketOptions,
|
|
4
|
+
executeJsonRequest,
|
|
5
|
+
executeResponseRequest,
|
|
3
6
|
logger
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Z3PCMYZO.js";
|
|
5
8
|
|
|
6
9
|
// src/core.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
10
|
+
import path15 from "path";
|
|
11
|
+
import os7 from "os";
|
|
9
12
|
import { createRequire } from "module";
|
|
10
13
|
import { accessSync } from "fs";
|
|
11
14
|
import { fileURLToPath } from "url";
|
|
@@ -94,6 +97,103 @@ var NoopActiveSpan = class {
|
|
|
94
97
|
}
|
|
95
98
|
};
|
|
96
99
|
var noopTracer = new NoopTracer();
|
|
100
|
+
var BasicTracer = class {
|
|
101
|
+
sink;
|
|
102
|
+
clock;
|
|
103
|
+
traceIdGenerator;
|
|
104
|
+
spanIdGenerator;
|
|
105
|
+
constructor({
|
|
106
|
+
sink,
|
|
107
|
+
clock = () => Date.now(),
|
|
108
|
+
traceIdGenerator = generateTraceId,
|
|
109
|
+
spanIdGenerator = generateSpanId
|
|
110
|
+
}) {
|
|
111
|
+
this.sink = sink;
|
|
112
|
+
this.clock = clock;
|
|
113
|
+
this.traceIdGenerator = traceIdGenerator;
|
|
114
|
+
this.spanIdGenerator = spanIdGenerator;
|
|
115
|
+
}
|
|
116
|
+
startSpan(name, options) {
|
|
117
|
+
const startTimeMs = options.startTimeMs ?? this.clock();
|
|
118
|
+
const context = createTraceContext({
|
|
119
|
+
parent: options.parent ?? null,
|
|
120
|
+
traceIdGenerator: this.traceIdGenerator,
|
|
121
|
+
spanIdGenerator: this.spanIdGenerator
|
|
122
|
+
});
|
|
123
|
+
return new RecordingActiveSpan({
|
|
124
|
+
context,
|
|
125
|
+
name,
|
|
126
|
+
surface: options.surface,
|
|
127
|
+
kind: options.kind ?? "internal",
|
|
128
|
+
attrs: options.attrs,
|
|
129
|
+
startTimeMs,
|
|
130
|
+
clock: this.clock,
|
|
131
|
+
sink: this.sink
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var RecordingActiveSpan = class {
|
|
136
|
+
context;
|
|
137
|
+
name;
|
|
138
|
+
surface;
|
|
139
|
+
kind;
|
|
140
|
+
startTimeMs;
|
|
141
|
+
clock;
|
|
142
|
+
sink;
|
|
143
|
+
events = [];
|
|
144
|
+
attrs;
|
|
145
|
+
ended = false;
|
|
146
|
+
constructor({
|
|
147
|
+
context,
|
|
148
|
+
name,
|
|
149
|
+
surface,
|
|
150
|
+
kind,
|
|
151
|
+
attrs,
|
|
152
|
+
startTimeMs,
|
|
153
|
+
clock,
|
|
154
|
+
sink
|
|
155
|
+
}) {
|
|
156
|
+
this.context = context;
|
|
157
|
+
this.name = name;
|
|
158
|
+
this.surface = surface;
|
|
159
|
+
this.kind = kind;
|
|
160
|
+
this.attrs = attrs;
|
|
161
|
+
this.startTimeMs = startTimeMs;
|
|
162
|
+
this.clock = clock;
|
|
163
|
+
this.sink = sink;
|
|
164
|
+
}
|
|
165
|
+
addEvent(name, attrs) {
|
|
166
|
+
if (this.ended) return;
|
|
167
|
+
this.events.push({
|
|
168
|
+
name,
|
|
169
|
+
timeMs: this.clock(),
|
|
170
|
+
...attrs ? { attrs } : {}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
end(status = "ok", options = {}) {
|
|
174
|
+
if (this.ended) return;
|
|
175
|
+
this.ended = true;
|
|
176
|
+
const endTimeMs = this.clock();
|
|
177
|
+
const attrs = mergeAttrs(this.attrs, options.attrs);
|
|
178
|
+
this.sink.record({
|
|
179
|
+
context: this.context,
|
|
180
|
+
name: this.name,
|
|
181
|
+
surface: this.surface,
|
|
182
|
+
kind: this.kind,
|
|
183
|
+
status,
|
|
184
|
+
startTimeMs: this.startTimeMs,
|
|
185
|
+
endTimeMs,
|
|
186
|
+
durationMs: Math.max(0, endTimeMs - this.startTimeMs),
|
|
187
|
+
...attrs ? { attrs } : {},
|
|
188
|
+
events: [...this.events]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
function mergeAttrs(base, extra) {
|
|
193
|
+
if (!base) return extra;
|
|
194
|
+
if (!extra) return base;
|
|
195
|
+
return { ...base, ...extra };
|
|
196
|
+
}
|
|
97
197
|
function generateTraceId() {
|
|
98
198
|
return randomNonZeroHex(TRACE_ID_HEX_LENGTH);
|
|
99
199
|
}
|
|
@@ -473,6 +573,9 @@ function summarizeToolInput(toolName, input) {
|
|
|
473
573
|
}
|
|
474
574
|
}
|
|
475
575
|
|
|
576
|
+
// ../shared/src/attachmentPreview.ts
|
|
577
|
+
var CSV_PREVIEW_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
578
|
+
|
|
476
579
|
// ../shared/src/testing/failpoints.ts
|
|
477
580
|
var NoopFailpointRegistry = class {
|
|
478
581
|
get enabled() {
|
|
@@ -548,54 +651,6 @@ var RUNTIMES = [
|
|
|
548
651
|
{ id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
|
|
549
652
|
{ id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
|
|
550
653
|
];
|
|
551
|
-
var RUNTIME_MODELS = {
|
|
552
|
-
claude: [
|
|
553
|
-
{ id: "sonnet", label: "Sonnet" },
|
|
554
|
-
{ id: "opus", label: "Opus" },
|
|
555
|
-
{ id: "haiku", label: "Haiku" }
|
|
556
|
-
],
|
|
557
|
-
codex: [
|
|
558
|
-
{ id: "gpt-5.5", label: "GPT-5.5" },
|
|
559
|
-
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
560
|
-
{ id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
|
561
|
-
{ id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark" },
|
|
562
|
-
{ id: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
|
|
563
|
-
{ id: "gpt-5.2", label: "GPT-5.2" },
|
|
564
|
-
{ id: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" },
|
|
565
|
-
{ id: "gpt-5.1-codex", label: "GPT-5.1 Codex" },
|
|
566
|
-
{ id: "gpt-5-codex", label: "GPT-5 Codex" },
|
|
567
|
-
{ id: "gpt-5", label: "GPT-5" }
|
|
568
|
-
],
|
|
569
|
-
copilot: [
|
|
570
|
-
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
571
|
-
{ id: "gpt-5.2", label: "GPT-5.2" },
|
|
572
|
-
{ id: "claude-4-sonnet", label: "Claude 4 Sonnet" },
|
|
573
|
-
{ id: "claude-4.5-sonnet", label: "Claude 4.5 Sonnet" }
|
|
574
|
-
],
|
|
575
|
-
cursor: [
|
|
576
|
-
{ id: "composer-2-fast", label: "Composer 2 Fast" },
|
|
577
|
-
{ id: "composer-2", label: "Composer 2" },
|
|
578
|
-
{ id: "auto", label: "Auto" }
|
|
579
|
-
],
|
|
580
|
-
gemini: [
|
|
581
|
-
{ id: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro (Preview)" },
|
|
582
|
-
{ id: "gemini-3-flash-preview", label: "Gemini 3 Flash (Preview)" },
|
|
583
|
-
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
|
584
|
-
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" }
|
|
585
|
-
],
|
|
586
|
-
opencode: [
|
|
587
|
-
{ id: "opencode/gpt-5-nano", label: "GPT-5 Nano (OpenCode)" },
|
|
588
|
-
{ id: "opencode/big-pickle", label: "Big Pickle (OpenCode)" },
|
|
589
|
-
{ id: "opencode/hy3-preview-free", label: "HY3 Preview Free (OpenCode)" },
|
|
590
|
-
{ id: "opencode/minimax-m2.5-free", label: "MiniMax M2.5 Free (OpenCode)" },
|
|
591
|
-
{ id: "opencode/nemotron-3-super-free", label: "Nemotron 3 Super Free (OpenCode)" }
|
|
592
|
-
],
|
|
593
|
-
// Kimi CLI resolves model keys from each user's local config, so the safest
|
|
594
|
-
// built-in option is to defer to whatever default model the CLI already uses.
|
|
595
|
-
kimi: [
|
|
596
|
-
{ id: "default", label: "Configured Default" }
|
|
597
|
-
]
|
|
598
|
-
};
|
|
599
654
|
var PLAN_CONFIG = {
|
|
600
655
|
free: {
|
|
601
656
|
displayName: "Hobby",
|
|
@@ -631,14 +686,15 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
631
686
|
};
|
|
632
687
|
|
|
633
688
|
// src/agentProcessManager.ts
|
|
634
|
-
import { mkdirSync as mkdirSync4, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
|
|
689
|
+
import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
635
690
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
636
691
|
import path11 from "path";
|
|
637
|
-
import
|
|
692
|
+
import os5 from "os";
|
|
638
693
|
|
|
639
694
|
// src/drivers/claude.ts
|
|
640
695
|
import { spawn } from "child_process";
|
|
641
|
-
import { writeFileSync as writeFileSync2 } from "fs";
|
|
696
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
697
|
+
import os from "os";
|
|
642
698
|
import path3 from "path";
|
|
643
699
|
|
|
644
700
|
// src/drivers/cliTransport.ts
|
|
@@ -735,8 +791,11 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
735
791
|
16. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
736
792
|
17. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
737
793
|
18. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
738
|
-
19. **\`slock reminder list\`** \u2014 List your reminders.
|
|
739
|
-
20. **\`slock reminder
|
|
794
|
+
19. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
795
|
+
20. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
796
|
+
21. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
797
|
+
22. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
798
|
+
23. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
740
799
|
|
|
741
800
|
The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
|
|
742
801
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -759,22 +818,23 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
759
818
|
9. **${taskClaimCmd}** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
|
|
760
819
|
10. **\`${t("unclaim_task")}\`** \u2014 Release your claim on a task.
|
|
761
820
|
11. **${taskUpdateCmd}** \u2014 Change a task's status (e.g. to in_review or done).
|
|
762
|
-
12. **\`${t("upload_file")}\`** \u2014 Upload a file to attach to a message. Returns an attachment ID to pass to ${sendCmd}.
|
|
821
|
+
12. **\`${t("upload_file")}\`** \u2014 Upload a file up to 50MB to attach to a message. Returns an attachment ID to pass to ${sendCmd}. Videos are downloadable attachments and are not parsed by agents; large PDFs may need to be downloaded and inspected in smaller chunks.
|
|
763
822
|
13. **\`${t("view_file")}\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
764
823
|
14. **${scheduleReminderCmd}** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
765
824
|
15. **${listRemindersCmd}** \u2014 List your reminders.
|
|
766
825
|
16. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
|
|
767
826
|
const reminderSection = `### Reminders
|
|
768
827
|
|
|
769
|
-
Use reminders for follow-up that depends on future state you cannot resolve now, whether user-requested or self-driven. A reminder is an author-owned, persistent, observable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people. If anchored to a message or thread, the receipt/fire system message is visible in that surface, but wake ownership does not transfer. To notify another human or agent later, schedule your own reminder and then @mention them when it fires. Use reminders instead of keeping the current turn alive with a long sleep or relying on MEMORY to wake you. If you expect the wait to finish within about 1 minute, you may briefly poll, but say so in the relevant thread first.
|
|
770
|
-
|
|
828
|
+
Use reminders for follow-up that depends on future state you cannot resolve now, whether user-requested or self-driven. A reminder is an author-owned, persistent, observable, snoozable, updatable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people. If anchored to a message or thread, the receipt/fire system message is visible in that surface, but wake ownership does not transfer. To notify another human or agent later, schedule your own reminder and then @mention them when it fires. Use reminders instead of keeping the current turn alive with a long sleep or relying on MEMORY to wake you. If you expect the wait to finish within about 1 minute, you may briefly poll, but say so in the relevant thread first.
|
|
829
|
+
When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
|
|
830
|
+
Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
|
|
771
831
|
Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
|
|
772
832
|
const sendingMessagesSection = isCli ? `### Sending messages
|
|
773
833
|
|
|
774
834
|
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'EOF'\` followed by the message body and \`EOF\`
|
|
775
|
-
- **Reply to a DM**: \`slock message send --target
|
|
835
|
+
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'EOF'\` followed by the message body and \`EOF\`
|
|
776
836
|
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'EOF'\` followed by the message body and \`EOF\`
|
|
777
|
-
- **Start a NEW DM**: \`slock message send --target
|
|
837
|
+
- **Start a NEW DM**: \`slock message send --target dm:@person-name <<'EOF'\` followed by the message body and \`EOF\`
|
|
778
838
|
|
|
779
839
|
Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
|
|
780
840
|
\`\`\`bash
|
|
@@ -814,10 +874,12 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
814
874
|
const discoverySection = isCli ? `### Discovering people and channels
|
|
815
875
|
|
|
816
876
|
Call \`slock server info\` to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
817
|
-
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`slock message read\` and \`slock channel members\`, but you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a regular channel you have joined, use \`slock channel leave --target "#channel-name"\`. To stop following a thread without leaving its parent channel, use \`slock thread unfollow --target "#channel-name:shortid"
|
|
877
|
+
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`slock message read\` and \`slock channel members\`, but you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a regular channel you have joined, use \`slock channel leave --target "#channel-name"\`. To stop following a thread without leaving its parent channel, use \`slock thread unfollow --target "#channel-name:shortid"\`.
|
|
878
|
+
Private channels are membership-gated. If \`slock server info\` shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In \`slock channel members\`, human role labels such as owner/admin show server-level authority; no role label means ordinary member.` : `### Discovering people and channels
|
|
818
879
|
|
|
819
880
|
Call ${serverInfoCmd} to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
820
|
-
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with ${readCmd}, but you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a regular channel you have joined, use \`${t("leave_channel")}
|
|
881
|
+
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with ${readCmd}, but you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a regular channel you have joined, use \`${t("leave_channel")}\`.
|
|
882
|
+
Private channels are membership-gated. If ${serverInfoCmd} shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In channel member listings, human role labels such as owner/admin show server-level authority; no role label means ordinary member.`;
|
|
821
883
|
const channelAwarenessSection = isCli ? `### Channel awareness
|
|
822
884
|
|
|
823
885
|
Each channel has a **name** and optionally a **description** that define its purpose (visible via \`slock server info\`). Respect them:
|
|
@@ -831,7 +893,7 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
831
893
|
- If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
|
|
832
894
|
const readingHistorySection = isCli ? `### Reading history
|
|
833
895
|
|
|
834
|
-
\`slock message read --channel "#channel-name"\` or \`slock message read --channel
|
|
896
|
+
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
835
897
|
|
|
836
898
|
To jump directly to a specific hit with nearby context, use \`slock message read --channel "..." --around "messageId"\` or \`slock message read --channel "..." --around 12345\`.` : `### Reading history
|
|
837
899
|
|
|
@@ -923,7 +985,7 @@ ${readCmd} shows messages in their current state. If a message was later convert
|
|
|
923
985
|
- Reuse existing tasks and threads instead of creating duplicates.
|
|
924
986
|
- Use ${taskCreateCmd} only for genuinely new subtasks or follow-up work that does not already have a canonical task.`;
|
|
925
987
|
const claimForEtiquette = isCli ? "`slock task claim`" : taskClaimCmd;
|
|
926
|
-
let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration.
|
|
988
|
+
let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration, serving as a shared message service for humans and agents who may be running on different computers.
|
|
927
989
|
|
|
928
990
|
## Who you are
|
|
929
991
|
|
|
@@ -1031,6 +1093,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1031
1093
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1032
1094
|
- When done, summarize the result.
|
|
1033
1095
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1096
|
+
- For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
|
|
1097
|
+
|
|
1098
|
+
\`\`\`html
|
|
1099
|
+
<details>
|
|
1100
|
+
<summary>Evidence, logs, or edge cases</summary>
|
|
1101
|
+
|
|
1102
|
+
Detailed notes go here.
|
|
1103
|
+
</details>
|
|
1104
|
+
\`\`\`
|
|
1105
|
+
|
|
1106
|
+
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1034
1107
|
|
|
1035
1108
|
### Conversation etiquette
|
|
1036
1109
|
|
|
@@ -1301,6 +1374,7 @@ var CLAUDE_DESKTOP_CLI_RELATIVE_PATH = path3.join("Applications", "Claude Code U
|
|
|
1301
1374
|
var CLAUDE_DESKTOP_CLI_SYSTEM_PATH = "/Applications/Claude Code URL Handler.app/Contents/MacOS/claude";
|
|
1302
1375
|
var CLAUDE_SYSTEM_PROMPT_FILE = "claude-system-prompt.md";
|
|
1303
1376
|
var CLAUDE_MCP_CONFIG_FILE = "claude-mcp-config.json";
|
|
1377
|
+
var SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME = "chat";
|
|
1304
1378
|
var CLAUDE_DISALLOWED_TOOLS = [
|
|
1305
1379
|
"EnterPlanMode",
|
|
1306
1380
|
"ExitPlanMode",
|
|
@@ -1326,6 +1400,80 @@ function probeClaude(deps = {}) {
|
|
|
1326
1400
|
version: readCommandVersion(command, [], deps) ?? void 0
|
|
1327
1401
|
};
|
|
1328
1402
|
}
|
|
1403
|
+
function isRecord(value) {
|
|
1404
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1405
|
+
}
|
|
1406
|
+
function expandClaudeMcpConfigVariables(raw, vars) {
|
|
1407
|
+
let expanded = raw;
|
|
1408
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
1409
|
+
expanded = expanded.replaceAll(`\${${name}}`, value);
|
|
1410
|
+
}
|
|
1411
|
+
return expanded;
|
|
1412
|
+
}
|
|
1413
|
+
function readClaudeMcpServers(configPath, vars = {}) {
|
|
1414
|
+
try {
|
|
1415
|
+
const parsed = JSON.parse(
|
|
1416
|
+
expandClaudeMcpConfigVariables(readFileSync(configPath, "utf8"), vars)
|
|
1417
|
+
);
|
|
1418
|
+
if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
|
|
1419
|
+
return parsed.mcpServers;
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
logger.warn(
|
|
1422
|
+
`[Claude] failed to read MCP config ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1423
|
+
);
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
function resolveClaudeConfigDir(ctx, home) {
|
|
1428
|
+
const configured = ctx.config.envVars?.CLAUDE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR;
|
|
1429
|
+
return configured && path3.isAbsolute(configured) ? configured : path3.join(home, ".claude");
|
|
1430
|
+
}
|
|
1431
|
+
function collectClaudeMcpConfigFiles(ctx, home) {
|
|
1432
|
+
const files = [];
|
|
1433
|
+
const pushIfFile = (candidate) => {
|
|
1434
|
+
try {
|
|
1435
|
+
if (existsSync2(candidate) && statSync(candidate).isFile()) {
|
|
1436
|
+
files.push({ path: candidate });
|
|
1437
|
+
}
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
pushIfFile(path3.join(home, ".claude.json"));
|
|
1442
|
+
pushIfFile(path3.join(ctx.workingDirectory, ".mcp.json"));
|
|
1443
|
+
const pluginRoot = path3.join(resolveClaudeConfigDir(ctx, home), "plugins");
|
|
1444
|
+
try {
|
|
1445
|
+
for (const entry of readdirSync(pluginRoot)) {
|
|
1446
|
+
const pluginPath = path3.join(pluginRoot, entry);
|
|
1447
|
+
const configPath = path3.join(pluginPath, ".mcp.json");
|
|
1448
|
+
try {
|
|
1449
|
+
if (existsSync2(configPath) && statSync(configPath).isFile()) {
|
|
1450
|
+
files.push({
|
|
1451
|
+
path: configPath,
|
|
1452
|
+
vars: { CLAUDE_PLUGIN_ROOT: pluginPath }
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
} catch {
|
|
1459
|
+
}
|
|
1460
|
+
return files;
|
|
1461
|
+
}
|
|
1462
|
+
function buildClaudeUserMcpServers(ctx, home) {
|
|
1463
|
+
const servers = /* @__PURE__ */ Object.create(null);
|
|
1464
|
+
for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
|
|
1465
|
+
const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
|
|
1466
|
+
if (!mcpServers) continue;
|
|
1467
|
+
for (const [name, server] of Object.entries(mcpServers)) {
|
|
1468
|
+
if (!isRecord(server)) {
|
|
1469
|
+
logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
servers[name] = server;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return servers;
|
|
1476
|
+
}
|
|
1329
1477
|
var ClaudeDriver = class {
|
|
1330
1478
|
id = "claude";
|
|
1331
1479
|
lifecycle = {
|
|
@@ -1381,36 +1529,46 @@ var ClaudeDriver = class {
|
|
|
1381
1529
|
}
|
|
1382
1530
|
return args;
|
|
1383
1531
|
}
|
|
1384
|
-
|
|
1532
|
+
buildRuntimeActionsMcpServer(ctx) {
|
|
1385
1533
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
1386
1534
|
const command = isTsSource ? "npx" : "node";
|
|
1387
1535
|
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
1536
|
+
return {
|
|
1537
|
+
command,
|
|
1538
|
+
args: [
|
|
1539
|
+
...bridgeArgs,
|
|
1540
|
+
"--agent-id",
|
|
1541
|
+
ctx.agentId,
|
|
1542
|
+
"--server-url",
|
|
1543
|
+
ctx.config.serverUrl,
|
|
1544
|
+
"--auth-token",
|
|
1545
|
+
ctx.config.authToken || ctx.daemonApiKey,
|
|
1546
|
+
"--runtime",
|
|
1547
|
+
this.id,
|
|
1548
|
+
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
1549
|
+
"--runtime-actions-only"
|
|
1550
|
+
]
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
buildRuntimeActionsMcpConfig(ctx, home = os.homedir()) {
|
|
1554
|
+
const userMcpServers = buildClaudeUserMcpServers(ctx, home);
|
|
1555
|
+
if (Object.prototype.hasOwnProperty.call(userMcpServers, SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME)) {
|
|
1556
|
+
logger.warn(
|
|
1557
|
+
`[Agent ${ctx.agentId}] Claude user MCP server "${SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME}" is reserved by Slock runtime actions and will be ignored`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1388
1560
|
return JSON.stringify({
|
|
1389
1561
|
mcpServers: {
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
args: [
|
|
1393
|
-
...bridgeArgs,
|
|
1394
|
-
"--agent-id",
|
|
1395
|
-
ctx.agentId,
|
|
1396
|
-
"--server-url",
|
|
1397
|
-
ctx.config.serverUrl,
|
|
1398
|
-
"--auth-token",
|
|
1399
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
1400
|
-
"--runtime",
|
|
1401
|
-
this.id,
|
|
1402
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
1403
|
-
"--runtime-actions-only"
|
|
1404
|
-
]
|
|
1405
|
-
}
|
|
1562
|
+
...userMcpServers,
|
|
1563
|
+
[SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME]: this.buildRuntimeActionsMcpServer(ctx)
|
|
1406
1564
|
}
|
|
1407
1565
|
});
|
|
1408
1566
|
}
|
|
1409
|
-
writeClaudeLaunchFiles(ctx, slockDir) {
|
|
1567
|
+
writeClaudeLaunchFiles(ctx, slockDir, home = os.homedir()) {
|
|
1410
1568
|
const systemPromptPath = path3.join(slockDir, CLAUDE_SYSTEM_PROMPT_FILE);
|
|
1411
1569
|
const mcpConfigPath = path3.join(slockDir, CLAUDE_MCP_CONFIG_FILE);
|
|
1412
1570
|
writeFileSync2(systemPromptPath, ctx.standingPrompt, { mode: 384 });
|
|
1413
|
-
writeFileSync2(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx), { mode: 384 });
|
|
1571
|
+
writeFileSync2(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx, home), { mode: 384 });
|
|
1414
1572
|
return { systemPromptPath, mcpConfigPath };
|
|
1415
1573
|
}
|
|
1416
1574
|
spawn(ctx) {
|
|
@@ -1558,8 +1716,8 @@ var ClaudeDriver = class {
|
|
|
1558
1716
|
|
|
1559
1717
|
// src/drivers/codex.ts
|
|
1560
1718
|
import { spawn as spawn2, execSync } from "child_process";
|
|
1561
|
-
import { existsSync as
|
|
1562
|
-
import
|
|
1719
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1720
|
+
import os2 from "os";
|
|
1563
1721
|
import path4 from "path";
|
|
1564
1722
|
function getCodexNotificationErrorMessage(params) {
|
|
1565
1723
|
const topLevelMessage = params?.message;
|
|
@@ -1573,7 +1731,7 @@ function getCodexNotificationErrorMessage(params) {
|
|
|
1573
1731
|
return null;
|
|
1574
1732
|
}
|
|
1575
1733
|
function ensureGitRepoForCodex(workingDirectory, deps = {}) {
|
|
1576
|
-
const existsSyncFn = deps.existsSyncFn ??
|
|
1734
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync3;
|
|
1577
1735
|
const execSyncFn = deps.execSyncFn ?? execSync;
|
|
1578
1736
|
const gitDir = path4.join(workingDirectory, ".git");
|
|
1579
1737
|
if (existsSyncFn(gitDir)) return;
|
|
@@ -1620,14 +1778,14 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1620
1778
|
try {
|
|
1621
1779
|
const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1622
1780
|
const candidate = path4.join(globalRoot, "@openai", "codex", "bin", "codex.js");
|
|
1623
|
-
if (
|
|
1781
|
+
if (existsSync3(candidate)) codexEntry = candidate;
|
|
1624
1782
|
} catch {
|
|
1625
1783
|
}
|
|
1626
1784
|
if (!codexEntry) {
|
|
1627
1785
|
try {
|
|
1628
1786
|
const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
|
|
1629
1787
|
const candidate = path4.join(path4.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
1630
|
-
if (
|
|
1788
|
+
if (existsSync3(candidate)) codexEntry = candidate;
|
|
1631
1789
|
} catch {
|
|
1632
1790
|
}
|
|
1633
1791
|
}
|
|
@@ -2047,12 +2205,12 @@ var CodexDriver = class {
|
|
|
2047
2205
|
return detectCodexModels();
|
|
2048
2206
|
}
|
|
2049
2207
|
};
|
|
2050
|
-
function detectCodexModels(home =
|
|
2208
|
+
function detectCodexModels(home = os2.homedir()) {
|
|
2051
2209
|
const cachePath = path4.join(home, ".codex", "models_cache.json");
|
|
2052
2210
|
const configPath = path4.join(home, ".codex", "config.toml");
|
|
2053
2211
|
let models = [];
|
|
2054
2212
|
try {
|
|
2055
|
-
const raw =
|
|
2213
|
+
const raw = readFileSync2(cachePath, "utf8");
|
|
2056
2214
|
const parsed = JSON.parse(raw);
|
|
2057
2215
|
const entries = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
2058
2216
|
for (const entry of entries) {
|
|
@@ -2069,7 +2227,7 @@ function detectCodexModels(home = os.homedir()) {
|
|
|
2069
2227
|
if (models.length === 0) return null;
|
|
2070
2228
|
let defaultModel;
|
|
2071
2229
|
try {
|
|
2072
|
-
const raw =
|
|
2230
|
+
const raw = readFileSync2(configPath, "utf8");
|
|
2073
2231
|
const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
|
|
2074
2232
|
if (match) defaultModel = match[1];
|
|
2075
2233
|
} catch {
|
|
@@ -2234,7 +2392,7 @@ var CopilotDriver = class {
|
|
|
2234
2392
|
|
|
2235
2393
|
// src/drivers/cursor.ts
|
|
2236
2394
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2237
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as
|
|
2395
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
2238
2396
|
import path6 from "path";
|
|
2239
2397
|
var CursorDriver = class {
|
|
2240
2398
|
id = "cursor";
|
|
@@ -2260,7 +2418,7 @@ var CursorDriver = class {
|
|
|
2260
2418
|
busyDeliveryMode = "none";
|
|
2261
2419
|
spawn(ctx) {
|
|
2262
2420
|
const cursorDir = path6.join(ctx.workingDirectory, ".cursor");
|
|
2263
|
-
if (!
|
|
2421
|
+
if (!existsSync4(cursorDir)) {
|
|
2264
2422
|
mkdirSync2(cursorDir, { recursive: true });
|
|
2265
2423
|
}
|
|
2266
2424
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
@@ -2412,18 +2570,14 @@ function runCursorModelsCommand() {
|
|
|
2412
2570
|
|
|
2413
2571
|
// src/drivers/gemini.ts
|
|
2414
2572
|
import { spawn as spawn5 } from "child_process";
|
|
2415
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3
|
|
2573
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
2416
2574
|
import path7 from "path";
|
|
2417
|
-
function buildGeminiSpawnEnv(ctx) {
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
// unless we explicitly trust the daemon-owned agent workspace.
|
|
2424
|
-
GEMINI_CLI_TRUST_WORKSPACE: "true",
|
|
2425
|
-
...ctx.config.envVars || {}
|
|
2426
|
-
};
|
|
2575
|
+
function buildGeminiSpawnEnv(ctx, platform = process.platform) {
|
|
2576
|
+
const { spawnEnv } = prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
|
|
2577
|
+
if (!Object.prototype.hasOwnProperty.call(ctx.config.envVars ?? {}, "GEMINI_CLI_TRUST_WORKSPACE")) {
|
|
2578
|
+
spawnEnv.GEMINI_CLI_TRUST_WORKSPACE = "true";
|
|
2579
|
+
}
|
|
2580
|
+
return spawnEnv;
|
|
2427
2581
|
}
|
|
2428
2582
|
var GeminiDriver = class {
|
|
2429
2583
|
id = "gemini";
|
|
@@ -2434,40 +2588,26 @@ var GeminiDriver = class {
|
|
|
2434
2588
|
inFlightWake: "spawn_new"
|
|
2435
2589
|
};
|
|
2436
2590
|
communication = {
|
|
2437
|
-
chat: "
|
|
2591
|
+
chat: "slock_cli",
|
|
2438
2592
|
runtimeControl: "mcp_runtime_actions"
|
|
2439
2593
|
};
|
|
2440
2594
|
session = {
|
|
2441
2595
|
recovery: "resume_or_fresh"
|
|
2442
2596
|
};
|
|
2443
2597
|
model = {
|
|
2444
|
-
detectedModelsVerifiedAs: "
|
|
2445
|
-
toLaunchSpec: (modelId) =>
|
|
2598
|
+
detectedModelsVerifiedAs: "suggestion_only",
|
|
2599
|
+
toLaunchSpec: (modelId) => modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] }
|
|
2446
2600
|
};
|
|
2447
2601
|
supportsStdinNotification = false;
|
|
2448
2602
|
mcpToolPrefix = "";
|
|
2449
2603
|
busyDeliveryMode = "none";
|
|
2604
|
+
usesSlockCliForCommunication = true;
|
|
2450
2605
|
sessionId = null;
|
|
2451
2606
|
sessionAnnounced = false;
|
|
2452
2607
|
spawn(ctx) {
|
|
2453
2608
|
this.sessionId = ctx.config.sessionId || null;
|
|
2454
2609
|
this.sessionAnnounced = false;
|
|
2455
|
-
|
|
2456
|
-
if (!existsSync4(geminiDir)) {
|
|
2457
|
-
mkdirSync3(geminiDir, { recursive: true });
|
|
2458
|
-
}
|
|
2459
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
2460
|
-
const mcpCommand = isTsSource ? "npx" : "node";
|
|
2461
|
-
const mcpArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
|
|
2462
|
-
const settingsPath = path7.join(geminiDir, "settings.json");
|
|
2463
|
-
writeFileSync5(settingsPath, JSON.stringify({
|
|
2464
|
-
mcpServers: {
|
|
2465
|
-
chat: {
|
|
2466
|
-
command: mcpCommand,
|
|
2467
|
-
args: mcpArgs
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}), "utf8");
|
|
2610
|
+
this.writeGeminiSettings(ctx);
|
|
2471
2611
|
const args = [
|
|
2472
2612
|
"--output-format",
|
|
2473
2613
|
"stream-json",
|
|
@@ -2541,28 +2681,64 @@ var GeminiDriver = class {
|
|
|
2541
2681
|
return null;
|
|
2542
2682
|
}
|
|
2543
2683
|
buildSystemPrompt(config, _agentId) {
|
|
2544
|
-
return
|
|
2684
|
+
return buildCliTransportSystemPrompt(config, {
|
|
2545
2685
|
toolPrefix: "",
|
|
2546
2686
|
extraCriticalRules: [
|
|
2547
|
-
"-
|
|
2687
|
+
"- Runtime Profile migration completion is the only exception to CLI-only operation: when a migration notice tells you to acknowledge with `runtime_profile_migration_done`, call the `runtime_profile_migration_done` tool with the exact `migration_key`; do not use `slock` CLI or reply in chat as the acknowledgment."
|
|
2688
|
+
],
|
|
2689
|
+
postStartupNotes: [
|
|
2690
|
+
"**Gemini runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
|
|
2548
2691
|
],
|
|
2549
|
-
postStartupNotes: [],
|
|
2550
2692
|
includeStdinNotificationSection: false,
|
|
2551
2693
|
messageNotificationStyle: "poll"
|
|
2552
2694
|
});
|
|
2553
2695
|
}
|
|
2696
|
+
writeGeminiSettings(ctx) {
|
|
2697
|
+
const geminiDir = path7.join(ctx.workingDirectory, ".gemini");
|
|
2698
|
+
mkdirSync3(geminiDir, { recursive: true });
|
|
2699
|
+
const settingsPath = path7.join(geminiDir, "settings.json");
|
|
2700
|
+
writeFileSync5(settingsPath, JSON.stringify(this.buildRuntimeActionsMcpSettings(ctx)), "utf8");
|
|
2701
|
+
}
|
|
2702
|
+
buildRuntimeActionsMcpSettings(ctx) {
|
|
2703
|
+
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
2704
|
+
const command = isTsSource ? "npx" : "node";
|
|
2705
|
+
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
2706
|
+
return {
|
|
2707
|
+
mcpServers: {
|
|
2708
|
+
chat: {
|
|
2709
|
+
command,
|
|
2710
|
+
args: [
|
|
2711
|
+
...bridgeArgs,
|
|
2712
|
+
"--agent-id",
|
|
2713
|
+
ctx.agentId,
|
|
2714
|
+
"--server-url",
|
|
2715
|
+
ctx.config.serverUrl,
|
|
2716
|
+
"--auth-token",
|
|
2717
|
+
ctx.config.authToken || ctx.daemonApiKey,
|
|
2718
|
+
"--runtime",
|
|
2719
|
+
this.id,
|
|
2720
|
+
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
2721
|
+
"--runtime-actions-only"
|
|
2722
|
+
]
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2554
2727
|
};
|
|
2555
2728
|
|
|
2556
2729
|
// src/drivers/kimi.ts
|
|
2557
2730
|
import { randomUUID } from "crypto";
|
|
2558
2731
|
import { spawn as spawn6 } from "child_process";
|
|
2559
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2560
|
-
import
|
|
2732
|
+
import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2733
|
+
import os3 from "os";
|
|
2561
2734
|
import path8 from "path";
|
|
2562
2735
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
2563
2736
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
2564
2737
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
2565
2738
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
2739
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
2740
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
2741
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
2566
2742
|
function parseToolArguments(raw) {
|
|
2567
2743
|
if (typeof raw !== "string") return raw;
|
|
2568
2744
|
try {
|
|
@@ -2571,6 +2747,73 @@ function parseToolArguments(raw) {
|
|
|
2571
2747
|
return raw;
|
|
2572
2748
|
}
|
|
2573
2749
|
}
|
|
2750
|
+
function readKimiConfigSource(home = os3.homedir(), env = process.env) {
|
|
2751
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
2752
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
2753
|
+
return {
|
|
2754
|
+
raw: inlineConfig,
|
|
2755
|
+
explicitPath: null,
|
|
2756
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
2760
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
|
|
2761
|
+
try {
|
|
2762
|
+
return {
|
|
2763
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
2764
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
2765
|
+
sourcePath: configPath
|
|
2766
|
+
};
|
|
2767
|
+
} catch {
|
|
2768
|
+
return {
|
|
2769
|
+
raw: null,
|
|
2770
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
2771
|
+
sourcePath: configPath
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
2776
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
2777
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
2778
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
2779
|
+
return spawnEnv;
|
|
2780
|
+
}
|
|
2781
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
2782
|
+
return {
|
|
2783
|
+
...process.env,
|
|
2784
|
+
...ctx.config.envVars || {},
|
|
2785
|
+
...overrideEnv || {}
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
2789
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
2790
|
+
const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
|
|
2791
|
+
const args = [];
|
|
2792
|
+
let configFilePath = null;
|
|
2793
|
+
let configContent = null;
|
|
2794
|
+
if (source.explicitPath) {
|
|
2795
|
+
configFilePath = source.explicitPath;
|
|
2796
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
2797
|
+
configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
2798
|
+
configContent = source.raw;
|
|
2799
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
2800
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
2801
|
+
chmodSync(configFilePath, 384);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (configFilePath) {
|
|
2805
|
+
args.push("--config-file", configFilePath);
|
|
2806
|
+
}
|
|
2807
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2808
|
+
args.push("--model", ctx.config.model);
|
|
2809
|
+
}
|
|
2810
|
+
return {
|
|
2811
|
+
args,
|
|
2812
|
+
env: buildKimiSpawnEnv(env),
|
|
2813
|
+
configFilePath,
|
|
2814
|
+
configContent
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2574
2817
|
var KimiDriver = class {
|
|
2575
2818
|
id = "kimi";
|
|
2576
2819
|
lifecycle = {
|
|
@@ -2587,7 +2830,25 @@ var KimiDriver = class {
|
|
|
2587
2830
|
};
|
|
2588
2831
|
model = {
|
|
2589
2832
|
detectedModelsVerifiedAs: "launchable",
|
|
2590
|
-
toLaunchSpec: (modelId) =>
|
|
2833
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
2834
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
2835
|
+
const launchCtx = {
|
|
2836
|
+
...ctx,
|
|
2837
|
+
config: {
|
|
2838
|
+
...ctx.config,
|
|
2839
|
+
model: modelId
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
2843
|
+
home: opts?.home,
|
|
2844
|
+
writeGeneratedConfig: false
|
|
2845
|
+
});
|
|
2846
|
+
return {
|
|
2847
|
+
args: launch.args,
|
|
2848
|
+
env: launch.env,
|
|
2849
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2591
2852
|
};
|
|
2592
2853
|
supportsStdinNotification = true;
|
|
2593
2854
|
mcpToolPrefix = "";
|
|
@@ -2639,6 +2900,7 @@ var KimiDriver = class {
|
|
|
2639
2900
|
}
|
|
2640
2901
|
}
|
|
2641
2902
|
}), "utf8");
|
|
2903
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
2642
2904
|
const args = [
|
|
2643
2905
|
"--wire",
|
|
2644
2906
|
"--yolo",
|
|
@@ -2647,16 +2909,13 @@ var KimiDriver = class {
|
|
|
2647
2909
|
"--mcp-config-file",
|
|
2648
2910
|
mcpConfigPath,
|
|
2649
2911
|
"--session",
|
|
2650
|
-
this.sessionId
|
|
2912
|
+
this.sessionId,
|
|
2913
|
+
...launch.args
|
|
2651
2914
|
];
|
|
2652
|
-
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2653
|
-
args.push("--model", ctx.config.model);
|
|
2654
|
-
}
|
|
2655
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
2656
2915
|
const proc = spawn6("kimi", args, {
|
|
2657
2916
|
cwd: ctx.workingDirectory,
|
|
2658
2917
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2659
|
-
env:
|
|
2918
|
+
env: launch.env,
|
|
2660
2919
|
shell: process.platform === "win32"
|
|
2661
2920
|
});
|
|
2662
2921
|
proc.stdin?.write(JSON.stringify({
|
|
@@ -2773,14 +3032,9 @@ var KimiDriver = class {
|
|
|
2773
3032
|
return detectKimiModels();
|
|
2774
3033
|
}
|
|
2775
3034
|
};
|
|
2776
|
-
function detectKimiModels(home =
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
try {
|
|
2780
|
-
raw = readFileSync2(configPath, "utf8");
|
|
2781
|
-
} catch {
|
|
2782
|
-
return null;
|
|
2783
|
-
}
|
|
3035
|
+
function detectKimiModels(home = os3.homedir(), opts = {}) {
|
|
3036
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
3037
|
+
if (raw === null) return null;
|
|
2784
3038
|
const models = [];
|
|
2785
3039
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
2786
3040
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -2800,9 +3054,9 @@ function detectKimiModels(home = os2.homedir()) {
|
|
|
2800
3054
|
}
|
|
2801
3055
|
|
|
2802
3056
|
// src/drivers/opencode.ts
|
|
2803
|
-
import { spawn as spawn7 } from "child_process";
|
|
2804
|
-
import { readFileSync as
|
|
2805
|
-
import
|
|
3057
|
+
import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
|
|
3058
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3059
|
+
import os4 from "os";
|
|
2806
3060
|
import path9 from "path";
|
|
2807
3061
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
2808
3062
|
var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
|
|
@@ -2810,6 +3064,14 @@ var SLOCK_AGENT_NAME = "slock";
|
|
|
2810
3064
|
var NO_MESSAGE_PROMPT = "No new messages are pending. Stop now.";
|
|
2811
3065
|
var FIRST_MESSAGE_TASK_PREFIX = "First message task (system-triggered):";
|
|
2812
3066
|
var MIN_SUPPORTED_OPENCODE_VERSION = "1.14.30";
|
|
3067
|
+
var OPENCODE_PROVIDER_LABELS = {
|
|
3068
|
+
opencode: "OpenCode",
|
|
3069
|
+
"opencode-go": "OpenCode Go",
|
|
3070
|
+
openai: "OpenAI",
|
|
3071
|
+
openrouter: "OpenRouter",
|
|
3072
|
+
deepseek: "DeepSeek",
|
|
3073
|
+
fusecode: "FuseCode"
|
|
3074
|
+
};
|
|
2813
3075
|
function buildChatBridgeCommand(ctx) {
|
|
2814
3076
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
2815
3077
|
return [
|
|
@@ -2842,10 +3104,10 @@ function parseUserOpenCodeConfig(ctx) {
|
|
|
2842
3104
|
const raw = ctx.config.envVars?.OPENCODE_CONFIG_CONTENT;
|
|
2843
3105
|
return parseOpenCodeConfigContent(raw);
|
|
2844
3106
|
}
|
|
2845
|
-
function readLocalOpenCodeConfig(home =
|
|
3107
|
+
function readLocalOpenCodeConfig(home = os4.homedir()) {
|
|
2846
3108
|
const configPath = path9.join(home, ".config", "opencode", "opencode.json");
|
|
2847
3109
|
try {
|
|
2848
|
-
return parseOpenCodeConfigContent(
|
|
3110
|
+
return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
|
|
2849
3111
|
} catch {
|
|
2850
3112
|
}
|
|
2851
3113
|
return {};
|
|
@@ -2891,7 +3153,7 @@ function mergeOpenCodeConfigs(localConfig, envConfig) {
|
|
|
2891
3153
|
}
|
|
2892
3154
|
};
|
|
2893
3155
|
}
|
|
2894
|
-
function buildOpenCodeConfig(ctx, home =
|
|
3156
|
+
function buildOpenCodeConfig(ctx, home = os4.homedir()) {
|
|
2895
3157
|
const userConfig = mergeOpenCodeConfigs(readLocalOpenCodeConfig(home), parseUserOpenCodeConfig(ctx));
|
|
2896
3158
|
const userAgents = recordField(userConfig.agent);
|
|
2897
3159
|
const userSlockAgent = recordField(userAgents[SLOCK_AGENT_NAME]);
|
|
@@ -2916,7 +3178,7 @@ function buildOpenCodeConfig(ctx, home = os3.homedir()) {
|
|
|
2916
3178
|
}
|
|
2917
3179
|
};
|
|
2918
3180
|
}
|
|
2919
|
-
function buildOpenCodeLaunchOptions(ctx, home =
|
|
3181
|
+
function buildOpenCodeLaunchOptions(ctx, home = os4.homedir()) {
|
|
2920
3182
|
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
2921
3183
|
const config = buildOpenCodeConfig(ctx, home);
|
|
2922
3184
|
const env = {
|
|
@@ -2943,24 +3205,92 @@ function buildOpenCodeLaunchOptions(ctx, home = os3.homedir()) {
|
|
|
2943
3205
|
args.push("--", turnPrompt);
|
|
2944
3206
|
return { args, env, config };
|
|
2945
3207
|
}
|
|
2946
|
-
function
|
|
2947
|
-
const
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3208
|
+
function parseOpenCodeModelsOutput(output) {
|
|
3209
|
+
const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
3210
|
+
const models = [];
|
|
3211
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3212
|
+
for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
|
|
3213
|
+
const line = rawLine.trim();
|
|
3214
|
+
if (!line || line.startsWith("{") || line.startsWith("}") || line.startsWith('"')) continue;
|
|
3215
|
+
if (/^opencode models\b/i.test(line) || /^list all available models$/i.test(line)) continue;
|
|
3216
|
+
if (!line.includes("/") || /\s/.test(line) || line.startsWith("-")) continue;
|
|
3217
|
+
if (seen.has(line)) continue;
|
|
3218
|
+
seen.add(line);
|
|
3219
|
+
models.push({
|
|
3220
|
+
id: line,
|
|
3221
|
+
label: formatOpenCodeModelLabel(line),
|
|
3222
|
+
verified: "launchable"
|
|
3223
|
+
});
|
|
2961
3224
|
}
|
|
2962
3225
|
return models.length > 0 ? { models } : null;
|
|
2963
3226
|
}
|
|
3227
|
+
function formatOpenCodeModelLabel(modelId) {
|
|
3228
|
+
const separatorIndex = modelId.indexOf("/");
|
|
3229
|
+
if (separatorIndex <= 0 || separatorIndex === modelId.length - 1) return modelId;
|
|
3230
|
+
const providerId = modelId.slice(0, separatorIndex);
|
|
3231
|
+
const modelName = modelId.slice(separatorIndex + 1);
|
|
3232
|
+
const providerLabel = OPENCODE_PROVIDER_LABELS[providerId] || humanizeOpenCodeSegment(providerId);
|
|
3233
|
+
const modelParts = modelName.split("/");
|
|
3234
|
+
const modelLabel = humanizeOpenCodeSegment(modelParts[modelParts.length - 1] || modelName);
|
|
3235
|
+
if (modelParts.length === 1) return `${modelLabel} \xB7 ${providerLabel}`;
|
|
3236
|
+
const upstreamLabel = modelParts.slice(0, -1).map(humanizeOpenCodeSegment).join(" / ");
|
|
3237
|
+
return `${modelLabel} \xB7 ${upstreamLabel} via ${providerLabel}`;
|
|
3238
|
+
}
|
|
3239
|
+
function humanizeOpenCodeSegment(value) {
|
|
3240
|
+
return value.replace(/\[(\d+)m\]/gi, "-$1m").split(/[-_]/).filter(Boolean).map(formatOpenCodeLabelToken).join(" ");
|
|
3241
|
+
}
|
|
3242
|
+
function formatOpenCodeLabelToken(token) {
|
|
3243
|
+
const normalized = token.toLowerCase();
|
|
3244
|
+
const specialCases = {
|
|
3245
|
+
ai: "AI",
|
|
3246
|
+
api: "API",
|
|
3247
|
+
b: "B",
|
|
3248
|
+
chatgpt: "ChatGPT",
|
|
3249
|
+
claude: "Claude",
|
|
3250
|
+
codestral: "Codestral",
|
|
3251
|
+
deepseek: "DeepSeek",
|
|
3252
|
+
flash: "Flash",
|
|
3253
|
+
free: "Free",
|
|
3254
|
+
gemini: "Gemini",
|
|
3255
|
+
glm: "GLM",
|
|
3256
|
+
gpt: "GPT",
|
|
3257
|
+
hy3: "HY3",
|
|
3258
|
+
kimi: "Kimi",
|
|
3259
|
+
m: "M",
|
|
3260
|
+
minimax: "MiniMax",
|
|
3261
|
+
nano: "Nano",
|
|
3262
|
+
nemotron: "Nemotron",
|
|
3263
|
+
omni: "Omni",
|
|
3264
|
+
opus: "Opus",
|
|
3265
|
+
pro: "Pro",
|
|
3266
|
+
sonnet: "Sonnet",
|
|
3267
|
+
super: "Super"
|
|
3268
|
+
};
|
|
3269
|
+
if (specialCases[normalized]) return specialCases[normalized];
|
|
3270
|
+
if (/^v\d+(\.\d+)?$/.test(normalized)) return normalized.toUpperCase();
|
|
3271
|
+
if (/^\d+m$/i.test(token)) return token.toUpperCase();
|
|
3272
|
+
if (/^\d+[bk]$/i.test(token)) return token.toUpperCase();
|
|
3273
|
+
if (/^m\d+(\.\d+)?$/i.test(token)) return token.toUpperCase();
|
|
3274
|
+
if (/^\d/.test(token)) return token;
|
|
3275
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
3276
|
+
}
|
|
3277
|
+
function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeModelsCommand) {
|
|
3278
|
+
const commandResult = runCommand(home);
|
|
3279
|
+
if (commandResult.error || commandResult.status !== 0) return null;
|
|
3280
|
+
return parseOpenCodeModelsOutput(commandResult.stdout);
|
|
3281
|
+
}
|
|
3282
|
+
function runOpenCodeModelsCommand(home) {
|
|
3283
|
+
const result = spawnSync2("opencode", ["models"], {
|
|
3284
|
+
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3285
|
+
encoding: "utf8",
|
|
3286
|
+
timeout: 5e3
|
|
3287
|
+
});
|
|
3288
|
+
return {
|
|
3289
|
+
status: result.status,
|
|
3290
|
+
stdout: String(result.stdout || ""),
|
|
3291
|
+
error: result.error
|
|
3292
|
+
};
|
|
3293
|
+
}
|
|
2964
3294
|
function isSystemFirstMessageTask(message) {
|
|
2965
3295
|
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
|
|
2966
3296
|
}
|
|
@@ -3236,9 +3566,39 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
|
|
|
3236
3566
|
}
|
|
3237
3567
|
|
|
3238
3568
|
// src/agentProcessManager.ts
|
|
3239
|
-
var DATA_DIR = path11.join(
|
|
3240
|
-
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS =
|
|
3569
|
+
var DATA_DIR = path11.join(os5.homedir(), ".slock", "agents");
|
|
3570
|
+
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
3241
3571
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
3572
|
+
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
3573
|
+
var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
3574
|
+
var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3575
|
+
".md",
|
|
3576
|
+
".txt",
|
|
3577
|
+
".json",
|
|
3578
|
+
".js",
|
|
3579
|
+
".ts",
|
|
3580
|
+
".jsx",
|
|
3581
|
+
".tsx",
|
|
3582
|
+
".yaml",
|
|
3583
|
+
".yml",
|
|
3584
|
+
".toml",
|
|
3585
|
+
".log",
|
|
3586
|
+
".csv",
|
|
3587
|
+
".xml",
|
|
3588
|
+
".html",
|
|
3589
|
+
".css",
|
|
3590
|
+
".sh",
|
|
3591
|
+
".py"
|
|
3592
|
+
]);
|
|
3593
|
+
var WORKSPACE_IMAGE_MIME_BY_EXTENSION = {
|
|
3594
|
+
".apng": "image/apng",
|
|
3595
|
+
".avif": "image/avif",
|
|
3596
|
+
".gif": "image/gif",
|
|
3597
|
+
".jpg": "image/jpeg",
|
|
3598
|
+
".jpeg": "image/jpeg",
|
|
3599
|
+
".png": "image/png",
|
|
3600
|
+
".webp": "image/webp"
|
|
3601
|
+
};
|
|
3242
3602
|
function readPositiveIntegerEnv(name, fallback) {
|
|
3243
3603
|
const raw = process.env[name];
|
|
3244
3604
|
if (!raw) return fallback;
|
|
@@ -3278,6 +3638,7 @@ function formatMessageTarget(message) {
|
|
|
3278
3638
|
function getMessageShortId(messageId) {
|
|
3279
3639
|
return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
|
|
3280
3640
|
}
|
|
3641
|
+
var RESPONSE_TARGET_HINT = "Reply in the channel or create/reply in a thread as appropriate; use each message's `target` and `msg` fields to choose the exact target.";
|
|
3281
3642
|
function findSessionJsonl(root, predicate) {
|
|
3282
3643
|
let visited = 0;
|
|
3283
3644
|
const maxEntries = 1e4;
|
|
@@ -3286,7 +3647,7 @@ function findSessionJsonl(root, predicate) {
|
|
|
3286
3647
|
if (depth < 0 || visited >= maxEntries) return null;
|
|
3287
3648
|
let entries;
|
|
3288
3649
|
try {
|
|
3289
|
-
entries =
|
|
3650
|
+
entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
|
|
3290
3651
|
} catch {
|
|
3291
3652
|
return null;
|
|
3292
3653
|
}
|
|
@@ -3332,11 +3693,11 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
3332
3693
|
return null;
|
|
3333
3694
|
}
|
|
3334
3695
|
}
|
|
3335
|
-
function resolveRuntimeSessionRef(runtime, sessionId, homeDir =
|
|
3696
|
+
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
3336
3697
|
const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
|
|
3337
3698
|
if (directPath) {
|
|
3338
3699
|
try {
|
|
3339
|
-
if (
|
|
3700
|
+
if (statSync2(directPath).isFile()) {
|
|
3340
3701
|
return { label: sessionId, path: directPath, runtime, reachable: true };
|
|
3341
3702
|
}
|
|
3342
3703
|
} catch {
|
|
@@ -3432,6 +3793,16 @@ function formatRuntimeProfileControlPrompt(messages) {
|
|
|
3432
3793
|
return null;
|
|
3433
3794
|
}
|
|
3434
3795
|
const body = controls.map(({ message }) => message.content).join("\n\n---\n\n");
|
|
3796
|
+
const hasMigration = controls.some(({ notification }) => notification?.kind === "migration");
|
|
3797
|
+
if (!hasMigration) {
|
|
3798
|
+
return [
|
|
3799
|
+
"Runtime Profile daemon release notice.",
|
|
3800
|
+
"",
|
|
3801
|
+
"Read the notice below before continuing. No chat reply or runtime control action is required for this notice \u2014 resume normal inbox processing afterward.",
|
|
3802
|
+
"",
|
|
3803
|
+
body
|
|
3804
|
+
].join("\n");
|
|
3805
|
+
}
|
|
3435
3806
|
return [
|
|
3436
3807
|
"Runtime Profile control notice.",
|
|
3437
3808
|
"",
|
|
@@ -3937,6 +4308,15 @@ function classifyTerminalFailure(ap) {
|
|
|
3937
4308
|
}
|
|
3938
4309
|
return null;
|
|
3939
4310
|
}
|
|
4311
|
+
function hasDirectStdinRecoveryEvidence(ap) {
|
|
4312
|
+
const candidates = [
|
|
4313
|
+
ap.lastRuntimeError,
|
|
4314
|
+
...ap.recentStderr
|
|
4315
|
+
].filter((value) => !!value);
|
|
4316
|
+
return candidates.some(
|
|
4317
|
+
(text) => /write_stdin failed|stdin is closed|closed for this session|session.*closed/i.test(text)
|
|
4318
|
+
);
|
|
4319
|
+
}
|
|
3940
4320
|
function isMissingResumeSession(ap) {
|
|
3941
4321
|
if (!ap.sessionId) return false;
|
|
3942
4322
|
const candidates = [
|
|
@@ -3953,6 +4333,66 @@ function isMissingResumeSession(ap) {
|
|
|
3953
4333
|
}
|
|
3954
4334
|
return false;
|
|
3955
4335
|
}
|
|
4336
|
+
function classifyActivityDetailForTrace(detail) {
|
|
4337
|
+
if (!detail) return void 0;
|
|
4338
|
+
if (detail === "Message received") return "message_received";
|
|
4339
|
+
if (detail === "Starting\u2026") return "starting";
|
|
4340
|
+
if (detail === "Running command\u2026") return "running_command";
|
|
4341
|
+
if (detail === "Checking messages\u2026") return "checking_messages";
|
|
4342
|
+
if (detail === "Compacting context") return "compacting_context";
|
|
4343
|
+
if (detail === "Context compaction finished") return "compaction_finished";
|
|
4344
|
+
if (detail === "Context compaction still running; no finish event observed") return "compaction_stale";
|
|
4345
|
+
if (detail === "Idle" || detail === "Process idle") return "idle";
|
|
4346
|
+
if (detail.startsWith("Restarting stalled ") && detail.endsWith(" runtime for queued message")) return "stalled_recovery";
|
|
4347
|
+
if (detail.startsWith("Runtime stalled: no runtime events for ")) return "runtime_stalled";
|
|
4348
|
+
return "other";
|
|
4349
|
+
}
|
|
4350
|
+
function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
4351
|
+
const context = [];
|
|
4352
|
+
if (ap.lastActivityDetail) {
|
|
4353
|
+
context.push(`after ${ap.lastActivityDetail}`);
|
|
4354
|
+
}
|
|
4355
|
+
if (ap.driver.busyDeliveryMode === "gated") {
|
|
4356
|
+
context.push(`phase=${ap.gatedSteering.phase}`);
|
|
4357
|
+
}
|
|
4358
|
+
if (ap.gatedSteering.outstandingToolUses > 0) {
|
|
4359
|
+
context.push(`tools=${ap.gatedSteering.outstandingToolUses}`);
|
|
4360
|
+
}
|
|
4361
|
+
if (ap.gatedSteering.compacting) {
|
|
4362
|
+
context.push("compacting");
|
|
4363
|
+
}
|
|
4364
|
+
if (ap.inbox.length > 0) {
|
|
4365
|
+
context.push(`queued=${ap.inbox.length}`);
|
|
4366
|
+
}
|
|
4367
|
+
const detail = [
|
|
4368
|
+
`Runtime stalled: no runtime events for ${staleForMinutes}m`,
|
|
4369
|
+
context.length > 0 ? ` (${context.join(", ")})` : ""
|
|
4370
|
+
].join("");
|
|
4371
|
+
return {
|
|
4372
|
+
detail,
|
|
4373
|
+
traceAttrs: {
|
|
4374
|
+
ageMs: staleForMs,
|
|
4375
|
+
staleForMinutes,
|
|
4376
|
+
lastActivity: ap.lastActivity,
|
|
4377
|
+
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
4378
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
4379
|
+
runtime: ap.config.runtime,
|
|
4380
|
+
model: ap.config.model,
|
|
4381
|
+
launchId: ap.launchId || void 0,
|
|
4382
|
+
sessionIdPresent: Boolean(ap.sessionId),
|
|
4383
|
+
inboxCount: ap.inbox.length,
|
|
4384
|
+
pendingNotificationCount: ap.pendingNotificationCount,
|
|
4385
|
+
processPidPresent: typeof ap.process.pid === "number",
|
|
4386
|
+
busyDeliveryMode: ap.driver.busyDeliveryMode,
|
|
4387
|
+
supportsStdinNotification: ap.driver.supportsStdinNotification,
|
|
4388
|
+
gatedPhase: ap.driver.busyDeliveryMode === "gated" ? ap.gatedSteering.phase : void 0,
|
|
4389
|
+
outstandingToolUses: ap.gatedSteering.outstandingToolUses,
|
|
4390
|
+
compacting: ap.gatedSteering.compacting,
|
|
4391
|
+
recentStderrCount: ap.recentStderr.length,
|
|
4392
|
+
recentStdoutCount: ap.recentStdout.length
|
|
4393
|
+
}
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
3956
4396
|
function getMessageDeliveryText(driver) {
|
|
3957
4397
|
return driver.supportsStdinNotification ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
|
|
3958
4398
|
}
|
|
@@ -3978,7 +4418,6 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3978
4418
|
// Prevent concurrent starts of same agent
|
|
3979
4419
|
queuedAgentStarts = /* @__PURE__ */ new Map();
|
|
3980
4420
|
agentStartQueue = [];
|
|
3981
|
-
activeAgentStartPermits = /* @__PURE__ */ new Set();
|
|
3982
4421
|
activeAgentStartCount = 0;
|
|
3983
4422
|
agentStartPumpTimer = null;
|
|
3984
4423
|
lastAgentStartAt = 0;
|
|
@@ -3998,6 +4437,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3998
4437
|
driverResolver;
|
|
3999
4438
|
defaultAgentEnvVarsProvider;
|
|
4000
4439
|
tracer;
|
|
4440
|
+
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
4001
4441
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
4002
4442
|
this.chatBridgePath = chatBridgePath;
|
|
4003
4443
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
@@ -4005,7 +4445,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4005
4445
|
this.daemonApiKey = daemonApiKey;
|
|
4006
4446
|
this.serverUrl = opts.serverUrl;
|
|
4007
4447
|
this.dataDir = opts.dataDir || DATA_DIR;
|
|
4008
|
-
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir ||
|
|
4448
|
+
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
|
|
4009
4449
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
4010
4450
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
4011
4451
|
this.tracer = opts.tracer ?? noopTracer;
|
|
@@ -4022,16 +4462,75 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4022
4462
|
)
|
|
4023
4463
|
);
|
|
4024
4464
|
}
|
|
4465
|
+
setTracer(tracer) {
|
|
4466
|
+
this.tracer = tracer;
|
|
4467
|
+
}
|
|
4468
|
+
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
4469
|
+
const span = this.tracer.startSpan(name, {
|
|
4470
|
+
parent: parseTraceparent(parentTraceparent),
|
|
4471
|
+
surface: "daemon",
|
|
4472
|
+
kind: "internal",
|
|
4473
|
+
attrs
|
|
4474
|
+
});
|
|
4475
|
+
span.end(status);
|
|
4476
|
+
}
|
|
4477
|
+
startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4478
|
+
return {
|
|
4479
|
+
agentId,
|
|
4480
|
+
launchId,
|
|
4481
|
+
runtime: config.runtime,
|
|
4482
|
+
model: config.model,
|
|
4483
|
+
session_id_present: Boolean(config.sessionId),
|
|
4484
|
+
launch_id_present: Boolean(launchId),
|
|
4485
|
+
wake_message_present: Boolean(wakeMessage),
|
|
4486
|
+
unread_channels_count: unreadSummary ? Object.keys(unreadSummary).length : 0,
|
|
4487
|
+
resume_prompt_present: Boolean(resumePrompt),
|
|
4488
|
+
queue_depth: this.agentStartQueue.length,
|
|
4489
|
+
active_starts: this.activeAgentStartCount,
|
|
4490
|
+
max_concurrent_starts: this.maxConcurrentAgentStarts,
|
|
4491
|
+
min_start_interval_ms: this.agentStartIntervalMs
|
|
4492
|
+
};
|
|
4493
|
+
}
|
|
4494
|
+
getDeliveryTraceContext(message) {
|
|
4495
|
+
return this.deliveryTraceContexts.get(message) ?? {};
|
|
4496
|
+
}
|
|
4497
|
+
deliveryTraceAttrs(agentId, message, attrs = {}) {
|
|
4498
|
+
const context = this.getDeliveryTraceContext(message);
|
|
4499
|
+
const deliveryCorrelationId = context.deliveryId ?? message.message_id;
|
|
4500
|
+
return {
|
|
4501
|
+
agentId,
|
|
4502
|
+
deliveryId: context.deliveryId,
|
|
4503
|
+
delivery_correlation_id: deliveryCorrelationId,
|
|
4504
|
+
channel_type: message.channel_type,
|
|
4505
|
+
sender_type: message.sender_type,
|
|
4506
|
+
messageId: message.message_id,
|
|
4507
|
+
message_id_present: Boolean(message.message_id),
|
|
4508
|
+
...attrs
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4025
4511
|
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4512
|
+
this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4026
4513
|
if (this.agents.has(agentId)) {
|
|
4514
|
+
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
4515
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4516
|
+
reason: "already_running"
|
|
4517
|
+
});
|
|
4027
4518
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
4028
4519
|
return;
|
|
4029
4520
|
}
|
|
4030
4521
|
if (this.agentsStarting.has(agentId)) {
|
|
4522
|
+
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
4523
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4524
|
+
reason: "already_starting"
|
|
4525
|
+
});
|
|
4031
4526
|
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
4032
4527
|
return;
|
|
4033
4528
|
}
|
|
4034
4529
|
if (this.queuedAgentStarts.has(agentId)) {
|
|
4530
|
+
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
4531
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4532
|
+
reason: "already_queued"
|
|
4533
|
+
});
|
|
4035
4534
|
logger.info(`[Agent ${agentId}] Start ignored (startup already queued)`);
|
|
4036
4535
|
return;
|
|
4037
4536
|
}
|
|
@@ -4048,6 +4547,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4048
4547
|
};
|
|
4049
4548
|
this.agentStartQueue.push(item);
|
|
4050
4549
|
this.queuedAgentStarts.set(agentId, item);
|
|
4550
|
+
this.recordDaemonTrace("daemon.agent.start.queued", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4051
4551
|
logger.info(
|
|
4052
4552
|
`[Agent ${agentId}] Start queued (queue=${this.agentStartQueue.length}, active=${this.activeAgentStartCount}, max=${this.maxConcurrentAgentStarts}, interval=${this.agentStartIntervalMs}ms)`
|
|
4053
4553
|
);
|
|
@@ -4063,6 +4563,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4063
4563
|
const elapsed = Date.now() - this.lastAgentStartAt;
|
|
4064
4564
|
const waitMs = shouldRateLimit ? Math.max(0, this.agentStartIntervalMs - elapsed) : 0;
|
|
4065
4565
|
if (waitMs > 0) {
|
|
4566
|
+
this.recordDaemonTrace("daemon.agent.start.rate_limited", {
|
|
4567
|
+
...this.startQueueTraceAttrs(next.agentId, next.config, next.wakeMessage, next.unreadSummary, next.resumePrompt, next.launchId),
|
|
4568
|
+
wait_ms: waitMs
|
|
4569
|
+
});
|
|
4066
4570
|
this.agentStartPumpTimer = setTimeout(() => {
|
|
4067
4571
|
this.agentStartPumpTimer = null;
|
|
4068
4572
|
this.pumpAgentStartQueue();
|
|
@@ -4072,23 +4576,31 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4072
4576
|
const item = this.agentStartQueue.shift();
|
|
4073
4577
|
if (!item) return;
|
|
4074
4578
|
if (this.queuedAgentStarts.get(item.agentId) !== item) {
|
|
4579
|
+
this.recordDaemonTrace("daemon.agent.start.skipped", {
|
|
4580
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4581
|
+
reason: "stale_queue_item"
|
|
4582
|
+
});
|
|
4075
4583
|
this.pumpAgentStartQueue();
|
|
4076
4584
|
return;
|
|
4077
4585
|
}
|
|
4078
4586
|
this.queuedAgentStarts.delete(item.agentId);
|
|
4079
4587
|
if (this.agents.has(item.agentId) || this.agentsStarting.has(item.agentId)) {
|
|
4588
|
+
this.recordDaemonTrace("daemon.agent.start.skipped", {
|
|
4589
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4590
|
+
reason: "already_running_or_starting"
|
|
4591
|
+
});
|
|
4080
4592
|
logger.info(`[Agent ${item.agentId}] Queued start skipped (already running or starting)`);
|
|
4081
4593
|
item.resolve();
|
|
4082
4594
|
this.pumpAgentStartQueue();
|
|
4083
4595
|
return;
|
|
4084
4596
|
}
|
|
4085
4597
|
this.activeAgentStartCount++;
|
|
4086
|
-
this.activeAgentStartPermits.add(item.agentId);
|
|
4087
4598
|
this.lastAgentStartAt = Date.now();
|
|
4088
4599
|
this.lastAgentStartAgentId = item.agentId;
|
|
4089
4600
|
logger.info(
|
|
4090
4601
|
`[Agent ${item.agentId}] Dequeued start (remaining=${this.agentStartQueue.length}, active=${this.activeAgentStartCount})`
|
|
4091
4602
|
);
|
|
4603
|
+
this.recordDaemonTrace("daemon.agent.start.dequeued", this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId));
|
|
4092
4604
|
this.startAgentNow(
|
|
4093
4605
|
item.agentId,
|
|
4094
4606
|
item.config,
|
|
@@ -4096,19 +4608,28 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4096
4608
|
item.unreadSummary,
|
|
4097
4609
|
item.resumePrompt,
|
|
4098
4610
|
item.launchId
|
|
4099
|
-
).then(
|
|
4100
|
-
this.
|
|
4611
|
+
).then(() => {
|
|
4612
|
+
this.releaseAgentStartSlot(item.agentId, "spawn attempted");
|
|
4613
|
+
item.resolve();
|
|
4614
|
+
}, (err) => {
|
|
4615
|
+
this.releaseAgentStartSlot(item.agentId, "start failed");
|
|
4101
4616
|
item.reject(err);
|
|
4102
4617
|
});
|
|
4103
4618
|
}
|
|
4104
|
-
|
|
4105
|
-
if (
|
|
4619
|
+
releaseAgentStartSlot(agentId, reason) {
|
|
4620
|
+
if (this.activeAgentStartCount <= 0) return;
|
|
4106
4621
|
this.activeAgentStartCount = Math.max(0, this.activeAgentStartCount - 1);
|
|
4622
|
+
this.recordDaemonTrace("daemon.agent.start.slot_released", {
|
|
4623
|
+
agentId,
|
|
4624
|
+
reason,
|
|
4625
|
+
active_starts: this.activeAgentStartCount,
|
|
4626
|
+
queue_depth: this.agentStartQueue.length,
|
|
4627
|
+
max_concurrent_starts: this.maxConcurrentAgentStarts
|
|
4628
|
+
});
|
|
4107
4629
|
logger.info(
|
|
4108
|
-
`[Agent ${agentId}] Start
|
|
4630
|
+
`[Agent ${agentId}] Start slot released (${reason}) (active=${this.activeAgentStartCount}, queue=${this.agentStartQueue.length})`
|
|
4109
4631
|
);
|
|
4110
4632
|
this.pumpAgentStartQueue();
|
|
4111
|
-
return true;
|
|
4112
4633
|
}
|
|
4113
4634
|
cancelQueuedAgentStart(agentId, reason) {
|
|
4114
4635
|
const item = this.queuedAgentStarts.get(agentId);
|
|
@@ -4120,6 +4641,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4120
4641
|
clearTimeout(this.agentStartPumpTimer);
|
|
4121
4642
|
this.agentStartPumpTimer = null;
|
|
4122
4643
|
}
|
|
4644
|
+
this.recordDaemonTrace("daemon.agent.start.cancelled", {
|
|
4645
|
+
...this.startQueueTraceAttrs(agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4646
|
+
reason
|
|
4647
|
+
}, "cancelled");
|
|
4123
4648
|
logger.info(`[Agent ${agentId}] Queued start cancelled (${reason})`);
|
|
4124
4649
|
item.resolve();
|
|
4125
4650
|
return true;
|
|
@@ -4127,6 +4652,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4127
4652
|
cancelAllQueuedAgentStarts(reason) {
|
|
4128
4653
|
for (const item of this.agentStartQueue) {
|
|
4129
4654
|
if (this.queuedAgentStarts.get(item.agentId) === item) {
|
|
4655
|
+
this.recordDaemonTrace("daemon.agent.start.cancelled", {
|
|
4656
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4657
|
+
reason
|
|
4658
|
+
}, "cancelled");
|
|
4130
4659
|
logger.info(`[Agent ${item.agentId}] Queued start cancelled (${reason})`);
|
|
4131
4660
|
item.resolve();
|
|
4132
4661
|
}
|
|
@@ -4141,14 +4670,23 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4141
4670
|
}
|
|
4142
4671
|
async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4143
4672
|
if (this.agents.has(agentId)) {
|
|
4673
|
+
this.recordDaemonTrace("daemon.agent.spawn.skipped", {
|
|
4674
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4675
|
+
reason: "already_running"
|
|
4676
|
+
});
|
|
4144
4677
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
4145
4678
|
return;
|
|
4146
4679
|
}
|
|
4147
4680
|
if (this.agentsStarting.has(agentId)) {
|
|
4681
|
+
this.recordDaemonTrace("daemon.agent.spawn.skipped", {
|
|
4682
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4683
|
+
reason: "already_starting"
|
|
4684
|
+
});
|
|
4148
4685
|
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
4149
4686
|
return;
|
|
4150
4687
|
}
|
|
4151
4688
|
this.agentsStarting.add(agentId);
|
|
4689
|
+
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4152
4690
|
try {
|
|
4153
4691
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
4154
4692
|
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
@@ -4208,6 +4746,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up, or respond to t
|
|
|
4208
4746
|
prompt += `
|
|
4209
4747
|
|
|
4210
4748
|
Respond as appropriate \u2014 ${dynamicReplyInstruction(driver)}, or take action as needed. Complete ALL your work before stopping.
|
|
4749
|
+
${RESPONSE_TARGET_HINT}
|
|
4211
4750
|
|
|
4212
4751
|
IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done \u2014 only stop when you have NO more work to do. ${getMessageDeliveryText(driver)}`;
|
|
4213
4752
|
prompt += getBusyDeliveryNote(driver);
|
|
@@ -4240,8 +4779,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4240
4779
|
});
|
|
4241
4780
|
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
4242
4781
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
4782
|
+
this.recordDaemonTrace("daemon.agent.spawn.deferred", {
|
|
4783
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4784
|
+
pending_messages_count: pendingMessages.length,
|
|
4785
|
+
reason: "defer_until_concrete_message"
|
|
4786
|
+
});
|
|
4243
4787
|
logger.info(`[Agent ${agentId}] Deferred ${driver.id} spawn until first concrete message`);
|
|
4244
|
-
this.releaseAgentStartPermit(agentId, "spawn deferred");
|
|
4245
4788
|
for (const message of pendingMessages) {
|
|
4246
4789
|
this.deliverMessage(agentId, message);
|
|
4247
4790
|
}
|
|
@@ -4258,6 +4801,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4258
4801
|
daemonApiKey: this.daemonApiKey,
|
|
4259
4802
|
launchId: launchId || null
|
|
4260
4803
|
});
|
|
4804
|
+
this.recordDaemonTrace("daemon.agent.spawn.created", {
|
|
4805
|
+
...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4806
|
+
detached: false,
|
|
4807
|
+
new_session: false,
|
|
4808
|
+
process_pid_present: typeof proc.pid === "number"
|
|
4809
|
+
});
|
|
4261
4810
|
const agentProcess = {
|
|
4262
4811
|
process: proc,
|
|
4263
4812
|
driver,
|
|
@@ -4292,7 +4841,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4292
4841
|
};
|
|
4293
4842
|
this.startingInboxes.delete(agentId);
|
|
4294
4843
|
this.agents.set(agentId, agentProcess);
|
|
4295
|
-
this.
|
|
4844
|
+
this.idleAgentConfigs.set(agentId, {
|
|
4845
|
+
config: { ...effectiveConfig, sessionId: effectiveConfig.sessionId || null },
|
|
4846
|
+
sessionId: effectiveConfig.sessionId || null,
|
|
4847
|
+
launchId: launchId || null
|
|
4848
|
+
});
|
|
4849
|
+
this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0);
|
|
4296
4850
|
this.agentsStarting.delete(agentId);
|
|
4297
4851
|
if (config.runtimeProfileControl) {
|
|
4298
4852
|
this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
|
|
@@ -4331,6 +4885,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4331
4885
|
proc.on("error", (err) => {
|
|
4332
4886
|
const current = this.agents.get(agentId);
|
|
4333
4887
|
if (current) current.spawnError = err.message;
|
|
4888
|
+
this.recordDaemonTrace("daemon.agent.process.error", {
|
|
4889
|
+
agentId,
|
|
4890
|
+
launchId: current?.launchId || void 0,
|
|
4891
|
+
runtime: config.runtime,
|
|
4892
|
+
model: config.model,
|
|
4893
|
+
error_class: err.name || typeof err
|
|
4894
|
+
}, "error");
|
|
4334
4895
|
logger.error(`[Agent ${agentId}] Process error: ${err.message}`);
|
|
4335
4896
|
});
|
|
4336
4897
|
proc.on("exit", (code, signal) => {
|
|
@@ -4339,13 +4900,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4339
4900
|
current.exitCode = code;
|
|
4340
4901
|
current.exitSignal = signal;
|
|
4341
4902
|
}
|
|
4903
|
+
this.recordDaemonTrace("daemon.agent.process.exited", {
|
|
4904
|
+
agentId,
|
|
4905
|
+
launchId: current?.launchId || void 0,
|
|
4906
|
+
runtime: config.runtime,
|
|
4907
|
+
model: config.model,
|
|
4908
|
+
exit_code: code,
|
|
4909
|
+
exit_signal: signal,
|
|
4910
|
+
clean_exit: code === 0,
|
|
4911
|
+
runtime_trace_active: Boolean(current?.runtimeTraceSpan),
|
|
4912
|
+
inbox_count: current?.inbox.length ?? 0,
|
|
4913
|
+
pending_notification_count: current?.pendingNotificationCount ?? 0
|
|
4914
|
+
});
|
|
4342
4915
|
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
4343
4916
|
});
|
|
4344
4917
|
proc.on("close", (code, signal) => {
|
|
4345
4918
|
if (this.agents.has(agentId)) {
|
|
4346
4919
|
const ap = this.agents.get(agentId);
|
|
4347
4920
|
if (ap.process !== proc) return;
|
|
4348
|
-
this.releaseAgentStartPermit(agentId, "process closed");
|
|
4349
4921
|
if (ap.notificationTimer) {
|
|
4350
4922
|
clearTimeout(ap.notificationTimer);
|
|
4351
4923
|
}
|
|
@@ -4403,7 +4975,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4403
4975
|
}
|
|
4404
4976
|
if (processEndedCleanly) {
|
|
4405
4977
|
let queuedWakeMessage;
|
|
4406
|
-
if (!ap.driver.supportsStdinNotification) {
|
|
4978
|
+
if (!ap.driver.supportsStdinNotification || ap.expectedTerminationReason === "stalled_recovery") {
|
|
4407
4979
|
while (ap.inbox.length > 0) {
|
|
4408
4980
|
const candidate = ap.inbox.shift();
|
|
4409
4981
|
if (this.shouldDeferWakeMessage(agentId, ap.driver, candidate)) continue;
|
|
@@ -4509,6 +5081,66 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4509
5081
|
}
|
|
4510
5082
|
return leftKeys.every((key) => left?.[key] === right?.[key]);
|
|
4511
5083
|
}
|
|
5084
|
+
enqueueRuntimeProfileNotification(agentId, ap, message, kind, key) {
|
|
5085
|
+
ap.inbox.push(message);
|
|
5086
|
+
if (ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
5087
|
+
ap.pendingNotificationCount++;
|
|
5088
|
+
if (ap.driver.busyDeliveryMode === "gated") {
|
|
5089
|
+
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
5090
|
+
reason: "runtime_profile",
|
|
5091
|
+
kind,
|
|
5092
|
+
pendingMessages: ap.inbox.length
|
|
5093
|
+
});
|
|
5094
|
+
} else if (!ap.notificationTimer) {
|
|
5095
|
+
ap.notificationTimer = setTimeout(() => {
|
|
5096
|
+
this.sendStdinNotification(agentId);
|
|
5097
|
+
}, 3e3);
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
this.recordDaemonTrace("daemon.agent.runtime_profile.routed", {
|
|
5101
|
+
agentId,
|
|
5102
|
+
kind,
|
|
5103
|
+
key_present: Boolean(key),
|
|
5104
|
+
outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
|
|
5105
|
+
runtime: ap.config.runtime,
|
|
5106
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5107
|
+
launchId: ap.launchId || void 0,
|
|
5108
|
+
inbox_count: ap.inbox.length,
|
|
5109
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
5110
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
5111
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification
|
|
5112
|
+
});
|
|
5113
|
+
logger.info(
|
|
5114
|
+
`[Agent ${agentId}] Queued runtime profile ${kind} ${key} for ${ap.sessionId ? "busy" : "pre-session"} ${ap.driver.id} delivery`
|
|
5115
|
+
);
|
|
5116
|
+
}
|
|
5117
|
+
queueRuntimeProfileNotificationDuringStart(agentId, message, kind, key) {
|
|
5118
|
+
const pending = this.startingInboxes.get(agentId) || [];
|
|
5119
|
+
pending.push(message);
|
|
5120
|
+
this.startingInboxes.set(agentId, pending);
|
|
5121
|
+
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
5122
|
+
this.recordDaemonTrace("daemon.agent.runtime_profile.routed", {
|
|
5123
|
+
agentId,
|
|
5124
|
+
kind,
|
|
5125
|
+
key_present: Boolean(key),
|
|
5126
|
+
outcome: "queued_during_start",
|
|
5127
|
+
startup_pending: true,
|
|
5128
|
+
starting_inbox_count: pending.length,
|
|
5129
|
+
launchId: queuedStart?.launchId
|
|
5130
|
+
});
|
|
5131
|
+
logger.info(`[Agent ${agentId}] Queued runtime profile ${kind} ${key} during startup`);
|
|
5132
|
+
}
|
|
5133
|
+
splitRuntimeProfileControlBatch(messages) {
|
|
5134
|
+
const controlMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message));
|
|
5135
|
+
if (controlMessages.length === 0 || controlMessages.length === messages.length) {
|
|
5136
|
+
return { nextMessages: messages, deferredMessages: [] };
|
|
5137
|
+
}
|
|
5138
|
+
const deferredMessages = messages.filter((message) => !runtimeProfileNotificationFromMessage(message));
|
|
5139
|
+
return { nextMessages: controlMessages, deferredMessages };
|
|
5140
|
+
}
|
|
5141
|
+
containsOrdinaryInboxMessage(messages) {
|
|
5142
|
+
return messages.some((message) => !runtimeProfileNotificationFromMessage(message));
|
|
5143
|
+
}
|
|
4512
5144
|
async stopAgent(agentId, { wait = false, silent = false } = {}) {
|
|
4513
5145
|
this.cancelQueuedAgentStart(agentId, "stop requested");
|
|
4514
5146
|
this.idleAgentConfigs.delete(agentId);
|
|
@@ -4519,7 +5151,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4519
5151
|
}
|
|
4520
5152
|
return;
|
|
4521
5153
|
}
|
|
4522
|
-
this.releaseAgentStartPermit(agentId, "stop requested");
|
|
4523
5154
|
if (ap.notificationTimer) {
|
|
4524
5155
|
clearTimeout(ap.notificationTimer);
|
|
4525
5156
|
}
|
|
@@ -4558,51 +5189,155 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4558
5189
|
});
|
|
4559
5190
|
}
|
|
4560
5191
|
}
|
|
4561
|
-
deliverMessage(agentId, message) {
|
|
5192
|
+
deliverMessage(agentId, message, traceContext = {}) {
|
|
5193
|
+
if (traceContext.deliveryId) {
|
|
5194
|
+
this.deliveryTraceContexts.set(message, traceContext);
|
|
5195
|
+
}
|
|
4562
5196
|
const ap = this.agents.get(agentId);
|
|
4563
5197
|
if (!ap) {
|
|
4564
5198
|
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
5199
|
+
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
4565
5200
|
const pending = this.startingInboxes.get(agentId) || [];
|
|
4566
5201
|
pending.push(message);
|
|
4567
5202
|
this.startingInboxes.set(agentId, pending);
|
|
4568
|
-
|
|
5203
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5204
|
+
outcome: "queued_during_start",
|
|
5205
|
+
accepted: true,
|
|
5206
|
+
process_present: false,
|
|
5207
|
+
startup_pending: true,
|
|
5208
|
+
starting_inbox_count: pending.length,
|
|
5209
|
+
launchId: queuedStart?.launchId
|
|
5210
|
+
}));
|
|
5211
|
+
return true;
|
|
4569
5212
|
}
|
|
4570
5213
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
4571
5214
|
if (cached) {
|
|
4572
5215
|
const driver = this.driverResolver(cached.config.runtime || "claude");
|
|
4573
5216
|
if (this.shouldDeferWakeMessage(agentId, driver, message)) {
|
|
4574
|
-
|
|
5217
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5218
|
+
outcome: "deferred_wake_message",
|
|
5219
|
+
accepted: true,
|
|
5220
|
+
process_present: false,
|
|
5221
|
+
cached_idle_config_present: true,
|
|
5222
|
+
runtime: cached.config.runtime,
|
|
5223
|
+
session_id_present: Boolean(cached.sessionId),
|
|
5224
|
+
launchId: cached.launchId || void 0
|
|
5225
|
+
}));
|
|
5226
|
+
return true;
|
|
4575
5227
|
}
|
|
4576
5228
|
logger.info(`[Agent ${agentId}] Starting from idle state for new message`);
|
|
4577
5229
|
this.idleAgentConfigs.delete(agentId);
|
|
5230
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5231
|
+
outcome: "auto_restart_from_idle",
|
|
5232
|
+
accepted: true,
|
|
5233
|
+
process_present: false,
|
|
5234
|
+
cached_idle_config_present: true,
|
|
5235
|
+
runtime: cached.config.runtime,
|
|
5236
|
+
session_id_present: Boolean(cached.sessionId),
|
|
5237
|
+
launchId: cached.launchId || void 0
|
|
5238
|
+
}));
|
|
4578
5239
|
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
|
|
4579
5240
|
logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
|
|
4580
5241
|
});
|
|
5242
|
+
return true;
|
|
4581
5243
|
}
|
|
4582
|
-
|
|
5244
|
+
logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
|
|
5245
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5246
|
+
outcome: "rejected_no_process",
|
|
5247
|
+
accepted: false,
|
|
5248
|
+
process_present: false,
|
|
5249
|
+
cached_idle_config_present: false
|
|
5250
|
+
}), "error");
|
|
5251
|
+
this.sendAgentStatus(agentId, "inactive", null);
|
|
5252
|
+
this.broadcastActivity(agentId, "offline", "Process unavailable; restart required");
|
|
5253
|
+
return false;
|
|
4583
5254
|
}
|
|
4584
5255
|
if (this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
|
|
4585
|
-
|
|
5256
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5257
|
+
outcome: "deferred_wake_message",
|
|
5258
|
+
accepted: true,
|
|
5259
|
+
process_present: true,
|
|
5260
|
+
runtime: ap.config.runtime,
|
|
5261
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5262
|
+
launchId: ap.launchId || void 0,
|
|
5263
|
+
is_idle: ap.isIdle,
|
|
5264
|
+
inbox_count: ap.inbox.length
|
|
5265
|
+
}));
|
|
5266
|
+
return true;
|
|
4586
5267
|
}
|
|
4587
5268
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
4588
5269
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
4589
5270
|
nextMessages.push(message);
|
|
4590
5271
|
ap.isIdle = false;
|
|
4591
|
-
this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery");
|
|
5272
|
+
this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", nextMessages);
|
|
4592
5273
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
4593
|
-
this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
|
|
4594
|
-
|
|
5274
|
+
const stdinAccepted = this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
|
|
5275
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5276
|
+
outcome: "stdin_idle_delivery",
|
|
5277
|
+
accepted: true,
|
|
5278
|
+
process_present: true,
|
|
5279
|
+
runtime: ap.config.runtime,
|
|
5280
|
+
session_id_present: true,
|
|
5281
|
+
launchId: ap.launchId || void 0,
|
|
5282
|
+
stdin_delivery_accepted: stdinAccepted,
|
|
5283
|
+
delivered_messages_count: nextMessages.length
|
|
5284
|
+
}));
|
|
5285
|
+
return true;
|
|
4595
5286
|
}
|
|
4596
5287
|
ap.inbox.push(message);
|
|
4597
|
-
if (
|
|
4598
|
-
|
|
5288
|
+
if (this.recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap)) {
|
|
5289
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5290
|
+
outcome: "queued_stalled_recovery",
|
|
5291
|
+
accepted: true,
|
|
5292
|
+
process_present: true,
|
|
5293
|
+
runtime: ap.config.runtime,
|
|
5294
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5295
|
+
launchId: ap.launchId || void 0,
|
|
5296
|
+
inbox_count: ap.inbox.length
|
|
5297
|
+
}));
|
|
5298
|
+
return true;
|
|
5299
|
+
}
|
|
5300
|
+
if (!ap.driver.supportsStdinNotification) {
|
|
5301
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5302
|
+
outcome: "queued_busy_non_stdin",
|
|
5303
|
+
accepted: true,
|
|
5304
|
+
process_present: true,
|
|
5305
|
+
runtime: ap.config.runtime,
|
|
5306
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5307
|
+
launchId: ap.launchId || void 0,
|
|
5308
|
+
inbox_count: ap.inbox.length
|
|
5309
|
+
}));
|
|
5310
|
+
return true;
|
|
5311
|
+
}
|
|
5312
|
+
if (!ap.sessionId) {
|
|
5313
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5314
|
+
outcome: "queued_before_session",
|
|
5315
|
+
accepted: true,
|
|
5316
|
+
process_present: true,
|
|
5317
|
+
runtime: ap.config.runtime,
|
|
5318
|
+
session_id_present: false,
|
|
5319
|
+
launchId: ap.launchId || void 0,
|
|
5320
|
+
inbox_count: ap.inbox.length
|
|
5321
|
+
}));
|
|
5322
|
+
return true;
|
|
5323
|
+
}
|
|
4599
5324
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
4600
5325
|
ap.pendingNotificationCount++;
|
|
4601
5326
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
4602
5327
|
reason: "busy_message",
|
|
4603
5328
|
pendingMessages: ap.inbox.length
|
|
4604
5329
|
});
|
|
4605
|
-
|
|
5330
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5331
|
+
outcome: "queued_busy_gated",
|
|
5332
|
+
accepted: true,
|
|
5333
|
+
process_present: true,
|
|
5334
|
+
runtime: ap.config.runtime,
|
|
5335
|
+
session_id_present: true,
|
|
5336
|
+
launchId: ap.launchId || void 0,
|
|
5337
|
+
inbox_count: ap.inbox.length,
|
|
5338
|
+
pending_notification_count: ap.pendingNotificationCount
|
|
5339
|
+
}));
|
|
5340
|
+
return true;
|
|
4606
5341
|
}
|
|
4607
5342
|
ap.pendingNotificationCount++;
|
|
4608
5343
|
if (!ap.notificationTimer) {
|
|
@@ -4610,6 +5345,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4610
5345
|
this.sendStdinNotification(agentId);
|
|
4611
5346
|
}, 3e3);
|
|
4612
5347
|
}
|
|
5348
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5349
|
+
outcome: "queued_busy_notification",
|
|
5350
|
+
accepted: true,
|
|
5351
|
+
process_present: true,
|
|
5352
|
+
runtime: ap.config.runtime,
|
|
5353
|
+
session_id_present: true,
|
|
5354
|
+
inbox_count: ap.inbox.length,
|
|
5355
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
5356
|
+
notification_timer_present: Boolean(ap.notificationTimer)
|
|
5357
|
+
}));
|
|
5358
|
+
return true;
|
|
4613
5359
|
}
|
|
4614
5360
|
async resetWorkspace(agentId) {
|
|
4615
5361
|
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
@@ -4643,6 +5389,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4643
5389
|
getIdleAgentSessionIds() {
|
|
4644
5390
|
const result = [];
|
|
4645
5391
|
for (const [agentId, { sessionId, launchId }] of this.idleAgentConfigs) {
|
|
5392
|
+
if (this.agents.has(agentId)) continue;
|
|
4646
5393
|
if (sessionId) result.push({ agentId, sessionId, launchId });
|
|
4647
5394
|
}
|
|
4648
5395
|
return result;
|
|
@@ -4694,7 +5441,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4694
5441
|
}
|
|
4695
5442
|
return reports;
|
|
4696
5443
|
}
|
|
4697
|
-
deliverRuntimeProfileNotification(agentId, key, kind, content) {
|
|
5444
|
+
deliverRuntimeProfileNotification(agentId, key, kind, content, traceparent) {
|
|
5445
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.inject", {
|
|
5446
|
+
parent: parseTraceparent(traceparent),
|
|
5447
|
+
surface: "daemon",
|
|
5448
|
+
kind: "consumer",
|
|
5449
|
+
attrs: {
|
|
5450
|
+
agentId,
|
|
5451
|
+
control_kind: kind,
|
|
5452
|
+
key_present: Boolean(key)
|
|
5453
|
+
}
|
|
5454
|
+
});
|
|
4698
5455
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4699
5456
|
const message = {
|
|
4700
5457
|
channel_id: "system",
|
|
@@ -4705,18 +5462,65 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4705
5462
|
sender_type: "system",
|
|
4706
5463
|
content,
|
|
4707
5464
|
timestamp: now,
|
|
4708
|
-
message_id: `${kind === "migration" ? RUNTIME_PROFILE_MIGRATION_MESSAGE_PREFIX : RUNTIME_PROFILE_DAEMON_NOTICE_MESSAGE_PREFIX}${key}
|
|
5465
|
+
message_id: `${kind === "migration" ? RUNTIME_PROFILE_MIGRATION_MESSAGE_PREFIX : RUNTIME_PROFILE_DAEMON_NOTICE_MESSAGE_PREFIX}${key}`,
|
|
5466
|
+
traceparent: formatTraceparent(span.context)
|
|
4709
5467
|
};
|
|
4710
5468
|
const ap = this.agents.get(agentId);
|
|
5469
|
+
if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.driver.busyDeliveryMode === "direct")) {
|
|
5470
|
+
this.enqueueRuntimeProfileNotification(agentId, ap, message, kind, key);
|
|
5471
|
+
span.end("ok", {
|
|
5472
|
+
attrs: {
|
|
5473
|
+
outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
|
|
5474
|
+
runtime: ap.config.runtime,
|
|
5475
|
+
launchId: ap.launchId || void 0,
|
|
5476
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5477
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
5478
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode
|
|
5479
|
+
}
|
|
5480
|
+
});
|
|
5481
|
+
return true;
|
|
5482
|
+
}
|
|
4711
5483
|
if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
|
|
4712
5484
|
ap.isIdle = false;
|
|
4713
5485
|
this.startRuntimeTrace(agentId, ap, "runtime-profile");
|
|
4714
|
-
this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
4715
|
-
|
|
5486
|
+
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
5487
|
+
span.end(written ? "ok" : "error", {
|
|
5488
|
+
attrs: {
|
|
5489
|
+
outcome: written ? "stdin_idle" : "stdin_failed",
|
|
5490
|
+
runtime: ap.config.runtime,
|
|
5491
|
+
launchId: ap.launchId || void 0,
|
|
5492
|
+
session_id_present: true,
|
|
5493
|
+
supports_stdin_notification: true,
|
|
5494
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode
|
|
5495
|
+
}
|
|
5496
|
+
});
|
|
5497
|
+
return written;
|
|
4716
5498
|
}
|
|
4717
5499
|
if (ap?.sessionId && ap.driver.busyDeliveryMode === "direct") {
|
|
4718
|
-
this.deliverMessagesViaStdin(agentId, ap, [message], "busy");
|
|
4719
|
-
|
|
5500
|
+
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "busy");
|
|
5501
|
+
span.end(written ? "ok" : "error", {
|
|
5502
|
+
attrs: {
|
|
5503
|
+
outcome: written ? "stdin_busy" : "stdin_failed",
|
|
5504
|
+
runtime: ap.config.runtime,
|
|
5505
|
+
launchId: ap.launchId || void 0,
|
|
5506
|
+
session_id_present: true,
|
|
5507
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
5508
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
return written;
|
|
5512
|
+
}
|
|
5513
|
+
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
5514
|
+
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
5515
|
+
this.queueRuntimeProfileNotificationDuringStart(agentId, message, kind, key);
|
|
5516
|
+
span.end("ok", {
|
|
5517
|
+
attrs: {
|
|
5518
|
+
outcome: "queued_during_start",
|
|
5519
|
+
startup_pending: true,
|
|
5520
|
+
launchId: queuedStart?.launchId
|
|
5521
|
+
}
|
|
5522
|
+
});
|
|
5523
|
+
return true;
|
|
4720
5524
|
}
|
|
4721
5525
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
4722
5526
|
if (cached) {
|
|
@@ -4726,9 +5530,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4726
5530
|
logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
|
|
4727
5531
|
this.idleAgentConfigs.set(agentId, cached);
|
|
4728
5532
|
});
|
|
4729
|
-
|
|
5533
|
+
span.end("ok", {
|
|
5534
|
+
attrs: {
|
|
5535
|
+
outcome: "restart_queued",
|
|
5536
|
+
runtime: cached.config.runtime,
|
|
5537
|
+
launchId: cached.launchId || void 0,
|
|
5538
|
+
session_id_present: Boolean(cached.sessionId)
|
|
5539
|
+
}
|
|
5540
|
+
});
|
|
5541
|
+
return true;
|
|
4730
5542
|
}
|
|
4731
5543
|
logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
|
|
5544
|
+
span.end("ok", { attrs: { outcome: "no_path" } });
|
|
5545
|
+
return false;
|
|
4732
5546
|
}
|
|
4733
5547
|
ackInjectedRuntimeProfileMessages(agentId, messages, launchId) {
|
|
4734
5548
|
for (const message of messages) {
|
|
@@ -4741,19 +5555,32 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4741
5555
|
type: "agent:runtime_profile:migration:ack",
|
|
4742
5556
|
agentId,
|
|
4743
5557
|
migrationKey: notification.key,
|
|
4744
|
-
launchId: launchId || void 0
|
|
5558
|
+
launchId: launchId || void 0,
|
|
5559
|
+
traceparent: message.traceparent
|
|
4745
5560
|
});
|
|
4746
5561
|
} else {
|
|
4747
5562
|
this.sendToServer({
|
|
4748
5563
|
type: "agent:runtime_profile:daemon_release_notice:ack",
|
|
4749
5564
|
agentId,
|
|
4750
5565
|
noticeKey: notification.key,
|
|
4751
|
-
launchId: launchId || void 0
|
|
5566
|
+
launchId: launchId || void 0,
|
|
5567
|
+
traceparent: message.traceparent
|
|
4752
5568
|
});
|
|
4753
5569
|
}
|
|
4754
5570
|
}
|
|
4755
5571
|
}
|
|
4756
5572
|
ackInjectedRuntimeProfileControl(agentId, control, launchId) {
|
|
5573
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.inject", {
|
|
5574
|
+
surface: "daemon",
|
|
5575
|
+
kind: "internal",
|
|
5576
|
+
attrs: {
|
|
5577
|
+
agentId,
|
|
5578
|
+
control_kind: control.kind,
|
|
5579
|
+
key_present: Boolean(control.key),
|
|
5580
|
+
launchId: launchId || void 0,
|
|
5581
|
+
source: "agent_config"
|
|
5582
|
+
}
|
|
5583
|
+
});
|
|
4757
5584
|
const title = runtimeProfileNotificationTitle(control.kind);
|
|
4758
5585
|
this.broadcastActivity(agentId, "working", title, [{ kind: "system", title, text: control.message }], launchId);
|
|
4759
5586
|
if (control.kind === "migration") {
|
|
@@ -4761,24 +5588,41 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4761
5588
|
type: "agent:runtime_profile:migration:ack",
|
|
4762
5589
|
agentId,
|
|
4763
5590
|
migrationKey: control.key,
|
|
4764
|
-
launchId: launchId || void 0
|
|
5591
|
+
launchId: launchId || void 0,
|
|
5592
|
+
traceparent: formatTraceparent(span.context)
|
|
4765
5593
|
});
|
|
4766
5594
|
} else {
|
|
4767
5595
|
this.sendToServer({
|
|
4768
5596
|
type: "agent:runtime_profile:daemon_release_notice:ack",
|
|
4769
5597
|
agentId,
|
|
4770
5598
|
noticeKey: control.key,
|
|
4771
|
-
launchId: launchId || void 0
|
|
5599
|
+
launchId: launchId || void 0,
|
|
5600
|
+
traceparent: formatTraceparent(span.context)
|
|
4772
5601
|
});
|
|
4773
5602
|
}
|
|
5603
|
+
span.end("ok", { attrs: { outcome: "agent_config_ack_sent" } });
|
|
4774
5604
|
}
|
|
4775
5605
|
sendRuntimeProfileWireReport(report) {
|
|
5606
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
5607
|
+
surface: "daemon",
|
|
5608
|
+
kind: "producer",
|
|
5609
|
+
attrs: {
|
|
5610
|
+
agentId: report.agentId,
|
|
5611
|
+
launchId: report.launchId || void 0,
|
|
5612
|
+
runtime: report.facts.runtime,
|
|
5613
|
+
model_present: Boolean(report.facts.model),
|
|
5614
|
+
session_ref_present: Boolean(report.facts.sessionRef),
|
|
5615
|
+
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
5616
|
+
}
|
|
5617
|
+
});
|
|
4776
5618
|
this.sendToServer({
|
|
4777
5619
|
type: "agent:runtime_profile",
|
|
4778
5620
|
agentId: report.agentId,
|
|
4779
5621
|
facts: report.facts,
|
|
4780
|
-
launchId: report.launchId || void 0
|
|
5622
|
+
launchId: report.launchId || void 0,
|
|
5623
|
+
traceparent: formatTraceparent(span.context)
|
|
4781
5624
|
});
|
|
5625
|
+
span.end("ok");
|
|
4782
5626
|
}
|
|
4783
5627
|
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId) {
|
|
4784
5628
|
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId));
|
|
@@ -4821,32 +5665,21 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4821
5665
|
}
|
|
4822
5666
|
const info = await stat2(resolved);
|
|
4823
5667
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
4824
|
-
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4825
|
-
".md",
|
|
4826
|
-
".txt",
|
|
4827
|
-
".json",
|
|
4828
|
-
".js",
|
|
4829
|
-
".ts",
|
|
4830
|
-
".jsx",
|
|
4831
|
-
".tsx",
|
|
4832
|
-
".yaml",
|
|
4833
|
-
".yml",
|
|
4834
|
-
".toml",
|
|
4835
|
-
".log",
|
|
4836
|
-
".csv",
|
|
4837
|
-
".xml",
|
|
4838
|
-
".html",
|
|
4839
|
-
".css",
|
|
4840
|
-
".sh",
|
|
4841
|
-
".py"
|
|
4842
|
-
]);
|
|
4843
5668
|
const ext = path11.extname(resolved).toLowerCase();
|
|
4844
|
-
if (
|
|
4845
|
-
|
|
5669
|
+
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
5670
|
+
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
5671
|
+
const content = await readFile(resolved, "utf-8");
|
|
5672
|
+
return { content, binary: false, size: info.size, encoding: "utf-8" };
|
|
5673
|
+
}
|
|
5674
|
+
const imageMimeType = WORKSPACE_IMAGE_MIME_BY_EXTENSION[ext];
|
|
5675
|
+
if (imageMimeType) {
|
|
5676
|
+
if (info.size > WORKSPACE_IMAGE_PREVIEW_MAX_BYTES) {
|
|
5677
|
+
return { content: null, binary: true, size: info.size, mimeType: imageMimeType };
|
|
5678
|
+
}
|
|
5679
|
+
const content = await readFile(resolved, "base64");
|
|
5680
|
+
return { content, binary: true, size: info.size, mimeType: imageMimeType, encoding: "base64" };
|
|
4846
5681
|
}
|
|
4847
|
-
|
|
4848
|
-
const content = await readFile(resolved, "utf-8");
|
|
4849
|
-
return { content, binary: false };
|
|
5682
|
+
return { content: null, binary: true, size: info.size };
|
|
4850
5683
|
}
|
|
4851
5684
|
// Skill scanning
|
|
4852
5685
|
// Per-runtime skill search paths (relative to home dir for global, workspace dir for workspace).
|
|
@@ -4866,7 +5699,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4866
5699
|
async listSkills(agentId, runtimeHint) {
|
|
4867
5700
|
const agent = this.agents.get(agentId);
|
|
4868
5701
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
4869
|
-
const home =
|
|
5702
|
+
const home = os5.homedir();
|
|
4870
5703
|
const workspaceDir = path11.join(this.dataDir, agentId);
|
|
4871
5704
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
4872
5705
|
const globalResults = await Promise.all(
|
|
@@ -5096,7 +5929,30 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5096
5929
|
this.clearCompactionWatchdog(ap);
|
|
5097
5930
|
this.broadcastActivity(agentId, "working", detail, [{ kind: "compaction_finished" }]);
|
|
5098
5931
|
}
|
|
5099
|
-
|
|
5932
|
+
messagesTraceAttrs(messages) {
|
|
5933
|
+
if (!messages || messages.length === 0) return {};
|
|
5934
|
+
const first = messages[0];
|
|
5935
|
+
const context = this.getDeliveryTraceContext(first);
|
|
5936
|
+
const notification = runtimeProfileNotificationFromMessage(first);
|
|
5937
|
+
if (notification) {
|
|
5938
|
+
return {
|
|
5939
|
+
messages_count: messages.length,
|
|
5940
|
+
message_id_present: Boolean(first.message_id),
|
|
5941
|
+
deliveryId: context.deliveryId,
|
|
5942
|
+
delivery_correlation_id: context.deliveryId,
|
|
5943
|
+
control_kind: notification.kind,
|
|
5944
|
+
key_present: Boolean(notification.key)
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
return {
|
|
5948
|
+
messages_count: messages.length,
|
|
5949
|
+
messageId: first.message_id,
|
|
5950
|
+
message_id_present: Boolean(first.message_id),
|
|
5951
|
+
deliveryId: context.deliveryId,
|
|
5952
|
+
delivery_correlation_id: context.deliveryId ?? first.message_id
|
|
5953
|
+
};
|
|
5954
|
+
}
|
|
5955
|
+
startRuntimeTrace(agentId, ap, reason, messages) {
|
|
5100
5956
|
if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
|
|
5101
5957
|
const span = this.tracer.startSpan("daemon.runtime.turn", {
|
|
5102
5958
|
surface: "daemon",
|
|
@@ -5106,10 +5962,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5106
5962
|
runtime: ap.config.runtime,
|
|
5107
5963
|
model: ap.config.model,
|
|
5108
5964
|
reason,
|
|
5109
|
-
hasSession: Boolean(ap.sessionId)
|
|
5965
|
+
hasSession: Boolean(ap.sessionId),
|
|
5966
|
+
...this.messagesTraceAttrs(messages)
|
|
5110
5967
|
}
|
|
5111
5968
|
});
|
|
5112
|
-
span.addEvent("daemon.turn.started", { reason });
|
|
5969
|
+
span.addEvent("daemon.turn.started", { reason, ...this.messagesTraceAttrs(messages) });
|
|
5113
5970
|
ap.runtimeTraceSpan = span;
|
|
5114
5971
|
return span;
|
|
5115
5972
|
}
|
|
@@ -5208,17 +6065,59 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5208
6065
|
if (staleForMs < RUNTIME_PROGRESS_STALE_MS) return false;
|
|
5209
6066
|
ap.runtimeProgressStaleSince = Date.now();
|
|
5210
6067
|
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
5211
|
-
|
|
6068
|
+
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
|
|
6069
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", diagnostic.traceAttrs);
|
|
6070
|
+
this.endRuntimeTrace(ap, "error", {
|
|
6071
|
+
outcome: "runtime-stalled",
|
|
5212
6072
|
ageMs: staleForMs,
|
|
5213
|
-
|
|
5214
|
-
|
|
6073
|
+
lastActivity: ap.lastActivity,
|
|
6074
|
+
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6075
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail)
|
|
6076
|
+
});
|
|
6077
|
+
this.broadcastActivity(agentId, "error", diagnostic.detail);
|
|
6078
|
+
return true;
|
|
6079
|
+
}
|
|
6080
|
+
recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap) {
|
|
6081
|
+
if (ap.inbox.length === 0) return false;
|
|
6082
|
+
if (ap.expectedTerminationReason === "stalled_recovery") {
|
|
6083
|
+
return true;
|
|
6084
|
+
}
|
|
6085
|
+
const directStdinRuntime = ap.driver.supportsStdinNotification && ap.driver.busyDeliveryMode === "direct";
|
|
6086
|
+
const canRestartDirectStdinProcess = directStdinRuntime && Boolean(ap.sessionId) && (ap.gatedSteering.outstandingToolUses === 0 || hasDirectStdinRecoveryEvidence(ap));
|
|
6087
|
+
const canRestartStalledProcess = !ap.driver.supportsStdinNotification || canRestartDirectStdinProcess;
|
|
6088
|
+
if (!canRestartStalledProcess) return false;
|
|
6089
|
+
const staleForMs = Date.now() - ap.lastRuntimeEventAt;
|
|
6090
|
+
if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgressStaleSince) return false;
|
|
6091
|
+
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
6092
|
+
ap.runtimeProgressStaleSince ??= Date.now();
|
|
6093
|
+
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
|
|
6094
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
|
|
6095
|
+
...diagnostic.traceAttrs,
|
|
6096
|
+
pendingMessages: ap.inbox.length,
|
|
6097
|
+
recovery: "terminate_for_queued_message"
|
|
5215
6098
|
});
|
|
5216
6099
|
this.endRuntimeTrace(ap, "error", {
|
|
5217
6100
|
outcome: "runtime-stalled",
|
|
5218
6101
|
ageMs: staleForMs,
|
|
5219
|
-
lastActivity: ap.lastActivity
|
|
6102
|
+
lastActivity: ap.lastActivity,
|
|
6103
|
+
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6104
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6105
|
+
pendingMessages: ap.inbox.length,
|
|
6106
|
+
recovery: "terminate_for_queued_message"
|
|
5220
6107
|
});
|
|
5221
|
-
|
|
6108
|
+
ap.expectedTerminationReason = "stalled_recovery";
|
|
6109
|
+
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
|
|
6110
|
+
logger.warn(
|
|
6111
|
+
`[Agent ${agentId}] ${runtimeLabel} process stalled for ${staleForMinutes}m with ${ap.inbox.length} queued message(s); terminating for restart`
|
|
6112
|
+
);
|
|
6113
|
+
this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
|
|
6114
|
+
try {
|
|
6115
|
+
ap.process.kill("SIGTERM");
|
|
6116
|
+
} catch (err) {
|
|
6117
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6118
|
+
logger.warn(`[Agent ${agentId}] Failed to terminate stalled ${runtimeLabel} process: ${reason}`);
|
|
6119
|
+
return false;
|
|
6120
|
+
}
|
|
5222
6121
|
return true;
|
|
5223
6122
|
}
|
|
5224
6123
|
/** Handle a single ParsedEvent from any runtime driver */
|
|
@@ -5322,7 +6221,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5322
6221
|
this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)");
|
|
5323
6222
|
this.flushPendingTrajectory(agentId);
|
|
5324
6223
|
if (ap) {
|
|
5325
|
-
this.releaseAgentStartPermit(agentId, "initial turn ended");
|
|
5326
6224
|
this.clearGatedInFlightBatch(agentId, ap, "turn_end");
|
|
5327
6225
|
if (event.sessionId) ap.sessionId = event.sessionId;
|
|
5328
6226
|
ap.gatedSteering.outstandingToolUses = 0;
|
|
@@ -5345,7 +6243,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5345
6243
|
}
|
|
5346
6244
|
} else {
|
|
5347
6245
|
ap.isIdle = true;
|
|
5348
|
-
|
|
6246
|
+
if (ap.lastRuntimeError) {
|
|
6247
|
+
this.broadcastActivity(agentId, "error", ap.lastRuntimeError);
|
|
6248
|
+
} else {
|
|
6249
|
+
this.broadcastActivity(agentId, "online", "Idle");
|
|
6250
|
+
}
|
|
5349
6251
|
}
|
|
5350
6252
|
this.endRuntimeTrace(ap, "ok", { outcome: "turn-completed" });
|
|
5351
6253
|
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
@@ -5384,6 +6286,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5384
6286
|
}
|
|
5385
6287
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", { message: event.message });
|
|
5386
6288
|
this.endRuntimeTrace(ap, "error", { outcome: "runtime-error", errorMessage: event.message });
|
|
6289
|
+
if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
|
|
6290
|
+
ap.isIdle = true;
|
|
6291
|
+
ap.pendingNotificationCount = 0;
|
|
6292
|
+
if (ap.notificationTimer) {
|
|
6293
|
+
clearTimeout(ap.notificationTimer);
|
|
6294
|
+
ap.notificationTimer = null;
|
|
6295
|
+
}
|
|
6296
|
+
logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
|
|
6297
|
+
}
|
|
5387
6298
|
}
|
|
5388
6299
|
this.broadcastActivity(agentId, "error", event.message, [
|
|
5389
6300
|
{ kind: "text", text: `Error: ${event.message}` }
|
|
@@ -5430,20 +6341,73 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5430
6341
|
const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
|
|
5431
6342
|
if (encoded) {
|
|
5432
6343
|
ap.process.stdin?.write(encoded + "\n");
|
|
6344
|
+
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
6345
|
+
agentId,
|
|
6346
|
+
runtime: ap.config.runtime,
|
|
6347
|
+
model: ap.config.model,
|
|
6348
|
+
launchId: ap.launchId || void 0,
|
|
6349
|
+
outcome: "written",
|
|
6350
|
+
mode: "busy",
|
|
6351
|
+
pending_notification_count: count,
|
|
6352
|
+
session_id_present: true
|
|
6353
|
+
});
|
|
6354
|
+
} else {
|
|
6355
|
+
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
6356
|
+
agentId,
|
|
6357
|
+
runtime: ap.config.runtime,
|
|
6358
|
+
model: ap.config.model,
|
|
6359
|
+
launchId: ap.launchId || void 0,
|
|
6360
|
+
outcome: "encode_failed",
|
|
6361
|
+
mode: "busy",
|
|
6362
|
+
pending_notification_count: count,
|
|
6363
|
+
session_id_present: true
|
|
6364
|
+
}, "error");
|
|
5433
6365
|
}
|
|
5434
6366
|
}
|
|
5435
6367
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
5436
6368
|
deliverMessagesViaStdin(agentId, ap, messages, mode) {
|
|
5437
6369
|
if (messages.length === 0) return true;
|
|
6370
|
+
const split = this.splitRuntimeProfileControlBatch(messages);
|
|
6371
|
+
if (split.deferredMessages.length > 0) {
|
|
6372
|
+
ap.inbox.unshift(...split.deferredMessages);
|
|
6373
|
+
ap.pendingNotificationCount += split.deferredMessages.length;
|
|
6374
|
+
messages = split.nextMessages;
|
|
6375
|
+
this.recordDaemonTrace("daemon.agent.runtime_profile.split_batch", {
|
|
6376
|
+
agentId,
|
|
6377
|
+
launchId: ap.launchId || void 0,
|
|
6378
|
+
runtime: ap.config.runtime,
|
|
6379
|
+
mode,
|
|
6380
|
+
delivered_control_messages_count: messages.length,
|
|
6381
|
+
deferred_messages_count: split.deferredMessages.length,
|
|
6382
|
+
inbox_count: ap.inbox.length,
|
|
6383
|
+
pending_notification_count: ap.pendingNotificationCount
|
|
6384
|
+
});
|
|
6385
|
+
}
|
|
6386
|
+
const traceAttrs = {
|
|
6387
|
+
agentId,
|
|
6388
|
+
launchId: ap.launchId || void 0,
|
|
6389
|
+
runtime: ap.config.runtime,
|
|
6390
|
+
model: ap.config.model,
|
|
6391
|
+
mode,
|
|
6392
|
+
messages_count: messages.length,
|
|
6393
|
+
session_id_present: Boolean(ap.sessionId),
|
|
6394
|
+
inbox_count: ap.inbox.length,
|
|
6395
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
6396
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
6397
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
6398
|
+
...this.messagesTraceAttrs(messages)
|
|
6399
|
+
};
|
|
5438
6400
|
const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
|
|
5439
6401
|
|
|
5440
6402
|
${formatIncomingMessage(messages[0], ap.driver)}
|
|
5441
6403
|
|
|
5442
|
-
Respond as appropriate. Complete all your work before stopping
|
|
6404
|
+
Respond as appropriate. Complete all your work before stopping.
|
|
6405
|
+
${RESPONSE_TARGET_HINT}` : `New messages received:
|
|
5443
6406
|
|
|
5444
6407
|
${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n")}
|
|
5445
6408
|
|
|
5446
|
-
Respond as appropriate. Complete all your work before stopping
|
|
6409
|
+
Respond as appropriate. Complete all your work before stopping.
|
|
6410
|
+
${RESPONSE_TARGET_HINT}`);
|
|
5447
6411
|
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
|
|
5448
6412
|
if (!encoded) {
|
|
5449
6413
|
ap.inbox.unshift(...messages);
|
|
@@ -5453,14 +6417,27 @@ Respond as appropriate. Complete all your work before stopping.`);
|
|
|
5453
6417
|
logger.warn(
|
|
5454
6418
|
`[Agent ${agentId}] Failed to encode ${mode} stdin delivery; re-queued ${messages.length === 1 ? "message" : `${messages.length} messages`}`
|
|
5455
6419
|
);
|
|
6420
|
+
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
6421
|
+
...traceAttrs,
|
|
6422
|
+
outcome: "encode_failed",
|
|
6423
|
+
requeued_messages_count: messages.length
|
|
6424
|
+
}, "error");
|
|
5456
6425
|
return false;
|
|
5457
6426
|
}
|
|
5458
6427
|
const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
|
|
5459
6428
|
logger.info(
|
|
5460
6429
|
`[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
|
|
5461
6430
|
);
|
|
6431
|
+
if (this.containsOrdinaryInboxMessage(messages)) {
|
|
6432
|
+
ap.lastRuntimeError = null;
|
|
6433
|
+
}
|
|
5462
6434
|
ap.process.stdin?.write(encoded + "\n");
|
|
5463
6435
|
this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
|
|
6436
|
+
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
6437
|
+
...traceAttrs,
|
|
6438
|
+
outcome: "written",
|
|
6439
|
+
stdin_write_attempted: true
|
|
6440
|
+
});
|
|
5464
6441
|
return true;
|
|
5465
6442
|
}
|
|
5466
6443
|
/** List ONE level of a directory — directories returned without children (lazy-loaded on demand) */
|
|
@@ -5505,6 +6482,16 @@ var systemClock = {
|
|
|
5505
6482
|
clearTimeout: (timer) => clearTimeout(timer)
|
|
5506
6483
|
};
|
|
5507
6484
|
var INBOUND_WATCHDOG_MS = 7e4;
|
|
6485
|
+
function durationMsBucket(ms) {
|
|
6486
|
+
if (ms == null || !Number.isFinite(ms) || ms < 0) return "unknown";
|
|
6487
|
+
if (ms === 0) return "0";
|
|
6488
|
+
if (ms <= 1e3) return "1s";
|
|
6489
|
+
if (ms <= 1e4) return "1s-10s";
|
|
6490
|
+
if (ms <= 3e4) return "10s-30s";
|
|
6491
|
+
if (ms <= 6e4) return "30s-60s";
|
|
6492
|
+
if (ms <= 12e4) return "60s-120s";
|
|
6493
|
+
return "120s+";
|
|
6494
|
+
}
|
|
5508
6495
|
var DaemonConnection = class {
|
|
5509
6496
|
ws = null;
|
|
5510
6497
|
options;
|
|
@@ -5516,6 +6503,8 @@ var DaemonConnection = class {
|
|
|
5516
6503
|
shouldConnect = true;
|
|
5517
6504
|
reconnectAttempt = 0;
|
|
5518
6505
|
lastDroppedSendLogAt = 0;
|
|
6506
|
+
lastInboundAt = null;
|
|
6507
|
+
lastInboundMessageKind = null;
|
|
5519
6508
|
constructor(options) {
|
|
5520
6509
|
this.options = options;
|
|
5521
6510
|
this.clock = options.clock ?? systemClock;
|
|
@@ -5550,6 +6539,10 @@ var DaemonConnection = class {
|
|
|
5550
6539
|
this.lastDroppedSendLogAt = now;
|
|
5551
6540
|
logger.warn(`[Daemon] Dropping outbound message while disconnected: ${msg.type}`);
|
|
5552
6541
|
}
|
|
6542
|
+
this.trace("daemon.connection.outbound_dropped", {
|
|
6543
|
+
message_type: msg.type,
|
|
6544
|
+
ws_ready_state: this.ws?.readyState ?? null
|
|
6545
|
+
});
|
|
5553
6546
|
}
|
|
5554
6547
|
get connected() {
|
|
5555
6548
|
return this.ws?.readyState === WebSocket.OPEN;
|
|
@@ -5563,25 +6556,49 @@ var DaemonConnection = class {
|
|
|
5563
6556
|
if (wsOptions?.agent) {
|
|
5564
6557
|
logger.info("[Daemon] Using configured proxy for WebSocket connection");
|
|
5565
6558
|
}
|
|
6559
|
+
this.trace("daemon.connection.connecting", {
|
|
6560
|
+
reconnect_attempt: this.reconnectAttempt,
|
|
6561
|
+
server_url_present: Boolean(this.options.serverUrl),
|
|
6562
|
+
proxy_present: Boolean(wsOptions?.agent)
|
|
6563
|
+
});
|
|
5566
6564
|
const ws = this.options.wsFactory ? this.options.wsFactory(wsUrl, wsOptions) : new WebSocket(wsUrl, wsOptions);
|
|
5567
6565
|
this.ws = ws;
|
|
5568
6566
|
ws.on("open", () => {
|
|
5569
6567
|
if (this.ws !== ws) return;
|
|
5570
6568
|
if (!this.shouldConnect) return;
|
|
5571
6569
|
logger.info("[Daemon] Connected to server");
|
|
6570
|
+
const priorReconnectAttempt = this.reconnectAttempt;
|
|
5572
6571
|
this.reconnectAttempt = 0;
|
|
5573
6572
|
this.reconnectDelay = this.options.minReconnectDelayMs ?? 1e3;
|
|
6573
|
+
this.markInbound("websocket_open");
|
|
5574
6574
|
this.resetWatchdog();
|
|
6575
|
+
this.trace("daemon.connection.connected", {
|
|
6576
|
+
reconnect_attempt: priorReconnectAttempt,
|
|
6577
|
+
inbound_watchdog_ms: this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS
|
|
6578
|
+
});
|
|
5575
6579
|
this.options.onConnect();
|
|
5576
6580
|
});
|
|
5577
6581
|
ws.on("message", (data) => {
|
|
5578
6582
|
if (this.ws !== ws) return;
|
|
5579
|
-
|
|
6583
|
+
let messageKind = "unknown";
|
|
5580
6584
|
try {
|
|
5581
6585
|
const msg = JSON.parse(data.toString());
|
|
6586
|
+
messageKind = msg.type;
|
|
6587
|
+
this.markInbound(messageKind);
|
|
6588
|
+
this.resetWatchdog();
|
|
6589
|
+
this.trace("daemon.connection.inbound_received", {
|
|
6590
|
+
message_type: messageKind,
|
|
6591
|
+
last_inbound_age_ms_bucket: "0"
|
|
6592
|
+
});
|
|
5582
6593
|
this.options.onMessage(msg);
|
|
5583
6594
|
} catch (err) {
|
|
6595
|
+
this.markInbound("invalid_json");
|
|
6596
|
+
this.resetWatchdog();
|
|
5584
6597
|
logger.error("[Daemon] Invalid message from server", err);
|
|
6598
|
+
this.trace("daemon.connection.invalid_message", {
|
|
6599
|
+
error_class: err instanceof Error ? err.name : typeof err,
|
|
6600
|
+
last_inbound_message_kind: "invalid_json"
|
|
6601
|
+
}, "error");
|
|
5585
6602
|
}
|
|
5586
6603
|
});
|
|
5587
6604
|
ws.on("close", (code, reasonBuffer) => {
|
|
@@ -5592,12 +6609,23 @@ var DaemonConnection = class {
|
|
|
5592
6609
|
logger.warn(
|
|
5593
6610
|
`[Daemon] Disconnected from server (code=${code}, reason=${JSON.stringify(reason)}, reconnecting=${this.shouldConnect})`
|
|
5594
6611
|
);
|
|
6612
|
+
this.trace("daemon.connection.disconnected", {
|
|
6613
|
+
close_code: code,
|
|
6614
|
+
close_reason_present: Boolean(reason),
|
|
6615
|
+
reconnecting: this.shouldConnect,
|
|
6616
|
+
reconnect_attempt: this.reconnectAttempt,
|
|
6617
|
+
last_inbound_message_kind: this.lastInboundMessageKind,
|
|
6618
|
+
last_inbound_age_ms_bucket: this.lastInboundAgeBucket()
|
|
6619
|
+
}, this.shouldConnect ? "cancelled" : "ok");
|
|
5595
6620
|
this.options.onDisconnect();
|
|
5596
6621
|
this.scheduleReconnect();
|
|
5597
6622
|
});
|
|
5598
6623
|
ws.on("error", (err) => {
|
|
5599
6624
|
if (this.ws !== ws) return;
|
|
5600
6625
|
logger.error(`[Daemon] WebSocket error: ${err.message}`);
|
|
6626
|
+
this.trace("daemon.connection.error", {
|
|
6627
|
+
error_class: err.name || "Error"
|
|
6628
|
+
}, "error");
|
|
5601
6629
|
});
|
|
5602
6630
|
}
|
|
5603
6631
|
scheduleReconnect() {
|
|
@@ -5605,6 +6633,10 @@ var DaemonConnection = class {
|
|
|
5605
6633
|
if (this.reconnectTimer) return;
|
|
5606
6634
|
this.reconnectAttempt += 1;
|
|
5607
6635
|
logger.info(`[Daemon] Reconnecting to server in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempt})`);
|
|
6636
|
+
this.trace("daemon.connection.reconnect_scheduled", {
|
|
6637
|
+
reconnect_attempt: this.reconnectAttempt,
|
|
6638
|
+
delay_ms: this.reconnectDelay
|
|
6639
|
+
});
|
|
5608
6640
|
this.reconnectTimer = this.clock.setTimeout(() => {
|
|
5609
6641
|
this.reconnectTimer = null;
|
|
5610
6642
|
this.doConnect();
|
|
@@ -5616,6 +6648,13 @@ var DaemonConnection = class {
|
|
|
5616
6648
|
const ms = this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS;
|
|
5617
6649
|
this.watchdogTimer = this.clock.setTimeout(() => {
|
|
5618
6650
|
logger.warn(`[Daemon] No inbound traffic for ${ms / 1e3}s \u2014 forcing reconnect`);
|
|
6651
|
+
this.trace("daemon.connection.watchdog_timeout", {
|
|
6652
|
+
inbound_watchdog_ms: ms,
|
|
6653
|
+
last_inbound_message_kind: this.lastInboundMessageKind,
|
|
6654
|
+
last_inbound_age_ms_bucket: this.lastInboundAgeBucket(),
|
|
6655
|
+
ws_ready_state: this.ws?.readyState ?? null,
|
|
6656
|
+
reconnecting: this.shouldConnect
|
|
6657
|
+
}, "error");
|
|
5619
6658
|
try {
|
|
5620
6659
|
this.ws?.terminate();
|
|
5621
6660
|
} catch {
|
|
@@ -5628,6 +6667,16 @@ var DaemonConnection = class {
|
|
|
5628
6667
|
this.watchdogTimer = null;
|
|
5629
6668
|
}
|
|
5630
6669
|
}
|
|
6670
|
+
markInbound(messageKind) {
|
|
6671
|
+
this.lastInboundAt = this.clock.now();
|
|
6672
|
+
this.lastInboundMessageKind = messageKind;
|
|
6673
|
+
}
|
|
6674
|
+
lastInboundAgeBucket() {
|
|
6675
|
+
return durationMsBucket(this.lastInboundAt == null ? null : this.clock.now() - this.lastInboundAt);
|
|
6676
|
+
}
|
|
6677
|
+
trace(name, attrs, status = "ok") {
|
|
6678
|
+
this.options.onTraceEvent?.(name, attrs, status);
|
|
6679
|
+
}
|
|
5631
6680
|
};
|
|
5632
6681
|
|
|
5633
6682
|
// src/reminderCache.ts
|
|
@@ -5711,10 +6760,10 @@ var ReminderCache = class {
|
|
|
5711
6760
|
|
|
5712
6761
|
// src/machineLock.ts
|
|
5713
6762
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
5714
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
5715
|
-
import
|
|
6763
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
6764
|
+
import os6 from "os";
|
|
5716
6765
|
import path12 from "path";
|
|
5717
|
-
var DEFAULT_MACHINE_STATE_ROOT = path12.join(
|
|
6766
|
+
var DEFAULT_MACHINE_STATE_ROOT = path12.join(os6.homedir(), ".slock", "machines");
|
|
5718
6767
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
5719
6768
|
var DaemonMachineLockConflictError = class extends Error {
|
|
5720
6769
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -5737,14 +6786,14 @@ function ownerPath(lockDir) {
|
|
|
5737
6786
|
}
|
|
5738
6787
|
function readOwner(lockDir) {
|
|
5739
6788
|
try {
|
|
5740
|
-
return JSON.parse(
|
|
6789
|
+
return JSON.parse(readFileSync5(ownerPath(lockDir), "utf8"));
|
|
5741
6790
|
} catch {
|
|
5742
6791
|
return null;
|
|
5743
6792
|
}
|
|
5744
6793
|
}
|
|
5745
6794
|
function lockAgeMs(lockDir) {
|
|
5746
6795
|
try {
|
|
5747
|
-
return Date.now() -
|
|
6796
|
+
return Date.now() - statSync3(lockDir).mtimeMs;
|
|
5748
6797
|
} catch {
|
|
5749
6798
|
return null;
|
|
5750
6799
|
}
|
|
@@ -5773,7 +6822,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
5773
6822
|
const owner = {
|
|
5774
6823
|
pid: process.pid,
|
|
5775
6824
|
token,
|
|
5776
|
-
hostname:
|
|
6825
|
+
hostname: os6.hostname(),
|
|
5777
6826
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5778
6827
|
serverUrl: options.serverUrl,
|
|
5779
6828
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
@@ -5815,6 +6864,418 @@ function acquireDaemonMachineLock(options) {
|
|
|
5815
6864
|
throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
|
|
5816
6865
|
}
|
|
5817
6866
|
|
|
6867
|
+
// src/localTraceSink.ts
|
|
6868
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
6869
|
+
import path13 from "path";
|
|
6870
|
+
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6871
|
+
var DEFAULT_MAX_FILES = 8;
|
|
6872
|
+
var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
|
|
6873
|
+
"serverId",
|
|
6874
|
+
"machineId",
|
|
6875
|
+
"agentId",
|
|
6876
|
+
"messageId",
|
|
6877
|
+
"launchId",
|
|
6878
|
+
"uploadId",
|
|
6879
|
+
"bundleId",
|
|
6880
|
+
"deliveryId",
|
|
6881
|
+
"deliveryCorrelationId",
|
|
6882
|
+
"delivery_correlation_id"
|
|
6883
|
+
]);
|
|
6884
|
+
var LocalRotatingTraceSink = class {
|
|
6885
|
+
traceDir;
|
|
6886
|
+
maxFileBytes;
|
|
6887
|
+
maxFiles;
|
|
6888
|
+
currentFile = null;
|
|
6889
|
+
currentSize = 0;
|
|
6890
|
+
sequence = 0;
|
|
6891
|
+
constructor(options) {
|
|
6892
|
+
this.traceDir = path13.join(options.machineDir, "traces");
|
|
6893
|
+
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
6894
|
+
this.maxFiles = Math.max(1, Math.floor(options.maxFiles ?? DEFAULT_MAX_FILES));
|
|
6895
|
+
}
|
|
6896
|
+
record(span) {
|
|
6897
|
+
try {
|
|
6898
|
+
const line = `${JSON.stringify(toLocalTraceRecord(span))}
|
|
6899
|
+
`;
|
|
6900
|
+
this.ensureFile(Buffer.byteLength(line));
|
|
6901
|
+
appendFileSync(this.currentFile, line, { encoding: "utf8" });
|
|
6902
|
+
this.currentSize += Buffer.byteLength(line);
|
|
6903
|
+
} catch {
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
getCurrentFile() {
|
|
6907
|
+
return this.currentFile;
|
|
6908
|
+
}
|
|
6909
|
+
ensureFile(nextBytes) {
|
|
6910
|
+
mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
|
|
6911
|
+
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes) {
|
|
6912
|
+
this.currentFile = path13.join(
|
|
6913
|
+
this.traceDir,
|
|
6914
|
+
`daemon-trace-${safeTimestamp(Date.now())}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
6915
|
+
);
|
|
6916
|
+
writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
|
|
6917
|
+
this.currentSize = statSync4(this.currentFile).size;
|
|
6918
|
+
this.pruneOldFiles();
|
|
6919
|
+
}
|
|
6920
|
+
}
|
|
6921
|
+
pruneOldFiles() {
|
|
6922
|
+
const files = readdirSync3(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
|
|
6923
|
+
const excess = files.length - this.maxFiles;
|
|
6924
|
+
if (excess <= 0) return;
|
|
6925
|
+
for (const file of files.slice(0, excess)) {
|
|
6926
|
+
rmSync3(path13.join(this.traceDir, file), { force: true });
|
|
6927
|
+
}
|
|
6928
|
+
}
|
|
6929
|
+
};
|
|
6930
|
+
function safeTimestamp(timeMs) {
|
|
6931
|
+
return new Date(timeMs).toISOString().replace(/[:.]/g, "-");
|
|
6932
|
+
}
|
|
6933
|
+
function toLocalTraceRecord(span) {
|
|
6934
|
+
return {
|
|
6935
|
+
type: "span",
|
|
6936
|
+
schema_version: 1,
|
|
6937
|
+
trace_id: span.context.traceId,
|
|
6938
|
+
span_id: span.context.spanId,
|
|
6939
|
+
parent_span_id: span.context.parentSpanId,
|
|
6940
|
+
name: span.name,
|
|
6941
|
+
surface: span.surface,
|
|
6942
|
+
kind: span.kind,
|
|
6943
|
+
status: span.status,
|
|
6944
|
+
start_time: new Date(span.startTimeMs).toISOString(),
|
|
6945
|
+
end_time: new Date(span.endTimeMs).toISOString(),
|
|
6946
|
+
duration_ms: span.durationMs,
|
|
6947
|
+
attrs: sanitizeAttrs(span.attrs),
|
|
6948
|
+
events: span.events.map(sanitizeEvent)
|
|
6949
|
+
};
|
|
6950
|
+
}
|
|
6951
|
+
function sanitizeEvent(event) {
|
|
6952
|
+
return {
|
|
6953
|
+
name: event.name,
|
|
6954
|
+
time: new Date(event.timeMs).toISOString(),
|
|
6955
|
+
attrs: sanitizeAttrs(event.attrs)
|
|
6956
|
+
};
|
|
6957
|
+
}
|
|
6958
|
+
function sanitizeAttrs(attrs) {
|
|
6959
|
+
if (!attrs) return void 0;
|
|
6960
|
+
const sanitized = {};
|
|
6961
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
6962
|
+
if (isDiagnosticIdAttr(key)) {
|
|
6963
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
6964
|
+
sanitized[key] = sanitizeValue(value);
|
|
6965
|
+
continue;
|
|
6966
|
+
}
|
|
6967
|
+
if (shouldDropAttr(key)) continue;
|
|
6968
|
+
sanitized[key] = sanitizeValue(value);
|
|
6969
|
+
}
|
|
6970
|
+
return sanitized;
|
|
6971
|
+
}
|
|
6972
|
+
function sanitizeValue(value) {
|
|
6973
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
6974
|
+
return value;
|
|
6975
|
+
}
|
|
6976
|
+
if (Array.isArray(value)) {
|
|
6977
|
+
return { items_count: value.length };
|
|
6978
|
+
}
|
|
6979
|
+
if (typeof value === "object") {
|
|
6980
|
+
return { object_present: true };
|
|
6981
|
+
}
|
|
6982
|
+
return String(value);
|
|
6983
|
+
}
|
|
6984
|
+
function shouldDropAttr(key) {
|
|
6985
|
+
const normalized = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
6986
|
+
if (/(^|_)(api_key|auth_token|token|secret|password|cookie|credential)(_|$)/i.test(normalized)) {
|
|
6987
|
+
return true;
|
|
6988
|
+
}
|
|
6989
|
+
if (/(^|_)(count|present|kind|mode|source|outcome|reason|class|status|bucket|ms|code|truncated)$/.test(normalized)) {
|
|
6990
|
+
return false;
|
|
6991
|
+
}
|
|
6992
|
+
if (/(^|_)id$/.test(normalized)) {
|
|
6993
|
+
return true;
|
|
6994
|
+
}
|
|
6995
|
+
return /(^|_)(prompt|content|text|message|body|request|response|command|argv|env|cwd|path|file|error|tool_args|tool_input|tool_output|stdout|stderr)(_|$)/i.test(normalized);
|
|
6996
|
+
}
|
|
6997
|
+
function isDiagnosticIdAttr(key) {
|
|
6998
|
+
return DIAGNOSTIC_ID_ATTRS.has(key);
|
|
6999
|
+
}
|
|
7000
|
+
|
|
7001
|
+
// src/traceBundleUpload.ts
|
|
7002
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
7003
|
+
import { gzipSync } from "zlib";
|
|
7004
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
7005
|
+
import path14 from "path";
|
|
7006
|
+
|
|
7007
|
+
// src/directUploadCapability.ts
|
|
7008
|
+
function joinUrl(base, path16) {
|
|
7009
|
+
return `${base.replace(/\/+$/, "")}${path16}`;
|
|
7010
|
+
}
|
|
7011
|
+
function jsonHeaders(apiKey) {
|
|
7012
|
+
return {
|
|
7013
|
+
"Content-Type": "application/json",
|
|
7014
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
7015
|
+
};
|
|
7016
|
+
}
|
|
7017
|
+
async function requestDaemonScopeAttestation({
|
|
7018
|
+
serverUrl,
|
|
7019
|
+
apiKey,
|
|
7020
|
+
scope,
|
|
7021
|
+
metadata,
|
|
7022
|
+
fetchImpl = fetch,
|
|
7023
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
|
|
7024
|
+
}) {
|
|
7025
|
+
const { response, data } = await executeJsonRequest(
|
|
7026
|
+
joinUrl(serverUrl, "/internal/machine/scope-attestation"),
|
|
7027
|
+
{
|
|
7028
|
+
method: "POST",
|
|
7029
|
+
headers: jsonHeaders(apiKey),
|
|
7030
|
+
body: JSON.stringify({
|
|
7031
|
+
scope,
|
|
7032
|
+
...metadata ? { metadata } : {}
|
|
7033
|
+
})
|
|
7034
|
+
},
|
|
7035
|
+
{
|
|
7036
|
+
toolName: "daemon_direct_upload.scope_attestation",
|
|
7037
|
+
target: scope,
|
|
7038
|
+
timeoutMs,
|
|
7039
|
+
fetchImpl
|
|
7040
|
+
}
|
|
7041
|
+
);
|
|
7042
|
+
if (!response.ok) {
|
|
7043
|
+
throw new Error(`Failed to request daemon scope attestation (${response.status})`);
|
|
7044
|
+
}
|
|
7045
|
+
return data;
|
|
7046
|
+
}
|
|
7047
|
+
async function createDirectUploadSession({
|
|
7048
|
+
serverUrl,
|
|
7049
|
+
apiKey,
|
|
7050
|
+
workerUrl,
|
|
7051
|
+
scope,
|
|
7052
|
+
createPath = "/api/uploads",
|
|
7053
|
+
body,
|
|
7054
|
+
attestationMetadata,
|
|
7055
|
+
fetchImpl = fetch,
|
|
7056
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
|
|
7057
|
+
}) {
|
|
7058
|
+
const capability = await requestDaemonScopeAttestation({
|
|
7059
|
+
serverUrl,
|
|
7060
|
+
apiKey,
|
|
7061
|
+
scope,
|
|
7062
|
+
metadata: attestationMetadata,
|
|
7063
|
+
fetchImpl,
|
|
7064
|
+
timeoutMs
|
|
7065
|
+
});
|
|
7066
|
+
const { response, data } = await executeJsonRequest(
|
|
7067
|
+
joinUrl(workerUrl, createPath),
|
|
7068
|
+
{
|
|
7069
|
+
method: "POST",
|
|
7070
|
+
headers: jsonHeaders(),
|
|
7071
|
+
body: JSON.stringify({
|
|
7072
|
+
...body,
|
|
7073
|
+
attestation: capability.attestation
|
|
7074
|
+
})
|
|
7075
|
+
},
|
|
7076
|
+
{
|
|
7077
|
+
toolName: "daemon_direct_upload.create",
|
|
7078
|
+
target: capability.audience,
|
|
7079
|
+
timeoutMs,
|
|
7080
|
+
fetchImpl
|
|
7081
|
+
}
|
|
7082
|
+
);
|
|
7083
|
+
if (!response.ok) {
|
|
7084
|
+
throw new Error(`Failed to create direct upload session (${response.status})`);
|
|
7085
|
+
}
|
|
7086
|
+
return { capability, response: data };
|
|
7087
|
+
}
|
|
7088
|
+
async function uploadWithSignedCapability({
|
|
7089
|
+
serverUrl,
|
|
7090
|
+
apiKey,
|
|
7091
|
+
workerUrl,
|
|
7092
|
+
scope,
|
|
7093
|
+
createPath = "/api/uploads",
|
|
7094
|
+
createBody,
|
|
7095
|
+
attestationMetadata,
|
|
7096
|
+
uploadBody,
|
|
7097
|
+
fetchImpl = fetch,
|
|
7098
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
|
|
7099
|
+
}) {
|
|
7100
|
+
const { capability, response: session } = await createDirectUploadSession({
|
|
7101
|
+
serverUrl,
|
|
7102
|
+
apiKey,
|
|
7103
|
+
workerUrl,
|
|
7104
|
+
scope,
|
|
7105
|
+
createPath,
|
|
7106
|
+
body: createBody,
|
|
7107
|
+
attestationMetadata,
|
|
7108
|
+
fetchImpl,
|
|
7109
|
+
timeoutMs
|
|
7110
|
+
});
|
|
7111
|
+
const { response: uploadResponse } = await executeResponseRequest(
|
|
7112
|
+
session.upload.url,
|
|
7113
|
+
{
|
|
7114
|
+
method: session.upload.method,
|
|
7115
|
+
headers: session.upload.headers ?? {},
|
|
7116
|
+
body: uploadBody
|
|
7117
|
+
},
|
|
7118
|
+
{
|
|
7119
|
+
toolName: "daemon_direct_upload.put",
|
|
7120
|
+
target: capability.audience,
|
|
7121
|
+
timeoutMs,
|
|
7122
|
+
fetchImpl
|
|
7123
|
+
}
|
|
7124
|
+
);
|
|
7125
|
+
if (!uploadResponse.ok) {
|
|
7126
|
+
throw new Error(`Failed to upload with signed capability (${uploadResponse.status})`);
|
|
7127
|
+
}
|
|
7128
|
+
return { capability, session, uploadResponse };
|
|
7129
|
+
}
|
|
7130
|
+
|
|
7131
|
+
// src/traceBundleUpload.ts
|
|
7132
|
+
var TRACE_UPLOAD_SCOPE = "daemon-trace-bundle:create";
|
|
7133
|
+
var DEFAULT_UPLOAD_INTERVAL_MS = 5 * 60 * 1e3;
|
|
7134
|
+
var DEFAULT_MIN_FILE_AGE_MS = 60 * 1e3;
|
|
7135
|
+
var DEFAULT_MAX_FILES_PER_RUN = 4;
|
|
7136
|
+
var DaemonTraceBundleUploader = class {
|
|
7137
|
+
options;
|
|
7138
|
+
timer = null;
|
|
7139
|
+
constructor(options) {
|
|
7140
|
+
this.options = options;
|
|
7141
|
+
}
|
|
7142
|
+
start() {
|
|
7143
|
+
if (this.timer) return;
|
|
7144
|
+
void this.uploadOnce();
|
|
7145
|
+
this.timer = setInterval(() => {
|
|
7146
|
+
void this.uploadOnce();
|
|
7147
|
+
}, this.options.intervalMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_INTERVAL_MS", DEFAULT_UPLOAD_INTERVAL_MS));
|
|
7148
|
+
}
|
|
7149
|
+
stop() {
|
|
7150
|
+
if (!this.timer) return;
|
|
7151
|
+
clearInterval(this.timer);
|
|
7152
|
+
this.timer = null;
|
|
7153
|
+
}
|
|
7154
|
+
async uploadOnce() {
|
|
7155
|
+
const files = await this.findUploadCandidates();
|
|
7156
|
+
let uploaded = 0;
|
|
7157
|
+
for (const file of files.slice(0, this.options.maxFilesPerRun ?? DEFAULT_MAX_FILES_PER_RUN)) {
|
|
7158
|
+
if (await this.uploadFile(file)) uploaded += 1;
|
|
7159
|
+
}
|
|
7160
|
+
return { attempted: files.length, uploaded };
|
|
7161
|
+
}
|
|
7162
|
+
async findUploadCandidates() {
|
|
7163
|
+
const traceDir = path14.join(this.options.machineDir, "traces");
|
|
7164
|
+
let names;
|
|
7165
|
+
try {
|
|
7166
|
+
names = await readdir3(traceDir);
|
|
7167
|
+
} catch {
|
|
7168
|
+
return [];
|
|
7169
|
+
}
|
|
7170
|
+
const now = Date.now();
|
|
7171
|
+
const minAgeMs = this.options.minFileAgeMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_MIN_FILE_AGE_MS", DEFAULT_MIN_FILE_AGE_MS);
|
|
7172
|
+
const currentFile = this.options.currentFileProvider?.();
|
|
7173
|
+
const candidates = [];
|
|
7174
|
+
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
7175
|
+
const file = path14.join(traceDir, name);
|
|
7176
|
+
if (currentFile && path14.resolve(file) === path14.resolve(currentFile)) continue;
|
|
7177
|
+
if (await this.isUploaded(file)) continue;
|
|
7178
|
+
try {
|
|
7179
|
+
const info = await stat3(file);
|
|
7180
|
+
if (!info.isFile() || info.size <= 0) continue;
|
|
7181
|
+
if (now - info.mtimeMs < minAgeMs) continue;
|
|
7182
|
+
candidates.push(file);
|
|
7183
|
+
} catch {
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
return candidates;
|
|
7187
|
+
}
|
|
7188
|
+
async uploadFile(file) {
|
|
7189
|
+
const span = this.options.tracer?.startSpan("daemon.bundle.upload", {
|
|
7190
|
+
surface: "daemon",
|
|
7191
|
+
kind: "producer",
|
|
7192
|
+
attrs: {
|
|
7193
|
+
file_present: true,
|
|
7194
|
+
worker_url_present: Boolean(this.options.workerUrl)
|
|
7195
|
+
}
|
|
7196
|
+
});
|
|
7197
|
+
try {
|
|
7198
|
+
const raw = await readFile2(file);
|
|
7199
|
+
if (raw.byteLength === 0) {
|
|
7200
|
+
span?.end("cancelled", { attrs: { outcome: "empty" } });
|
|
7201
|
+
return false;
|
|
7202
|
+
}
|
|
7203
|
+
const gzipped = gzipSync(raw);
|
|
7204
|
+
const bundleSha256 = sha256Hex(gzipped);
|
|
7205
|
+
const bundleId = randomUUID3();
|
|
7206
|
+
await uploadWithSignedCapability({
|
|
7207
|
+
serverUrl: this.options.serverUrl,
|
|
7208
|
+
apiKey: this.options.apiKey,
|
|
7209
|
+
workerUrl: this.options.workerUrl,
|
|
7210
|
+
scope: TRACE_UPLOAD_SCOPE,
|
|
7211
|
+
createPath: "/api/trace-bundles",
|
|
7212
|
+
attestationMetadata: {
|
|
7213
|
+
bundleId,
|
|
7214
|
+
bundleSha256,
|
|
7215
|
+
bundleSizeBytes: gzipped.byteLength
|
|
7216
|
+
},
|
|
7217
|
+
createBody: {
|
|
7218
|
+
bundleSha256,
|
|
7219
|
+
bundleSizeBytes: gzipped.byteLength
|
|
7220
|
+
},
|
|
7221
|
+
uploadBody: new Blob([new Uint8Array(gzipped)], { type: "application/x-ndjson" }),
|
|
7222
|
+
fetchImpl: this.options.fetchImpl
|
|
7223
|
+
});
|
|
7224
|
+
await this.markUploaded(file, {
|
|
7225
|
+
bundleId,
|
|
7226
|
+
bundleSha256,
|
|
7227
|
+
bundleSizeBytes: gzipped.byteLength
|
|
7228
|
+
});
|
|
7229
|
+
span?.end("ok", {
|
|
7230
|
+
attrs: {
|
|
7231
|
+
bundleId,
|
|
7232
|
+
bundle_size_bytes: gzipped.byteLength
|
|
7233
|
+
}
|
|
7234
|
+
});
|
|
7235
|
+
return true;
|
|
7236
|
+
} catch (err) {
|
|
7237
|
+
span?.end("error", {
|
|
7238
|
+
attrs: {
|
|
7239
|
+
error_class: err instanceof Error ? err.name : "Error",
|
|
7240
|
+
error_message_present: err instanceof Error && Boolean(err.message)
|
|
7241
|
+
}
|
|
7242
|
+
});
|
|
7243
|
+
return false;
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
uploadStatePath(file) {
|
|
7247
|
+
const stateDir = path14.join(this.options.machineDir, "trace-uploads");
|
|
7248
|
+
return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
|
|
7249
|
+
}
|
|
7250
|
+
async isUploaded(file) {
|
|
7251
|
+
try {
|
|
7252
|
+
await stat3(this.uploadStatePath(file));
|
|
7253
|
+
return true;
|
|
7254
|
+
} catch {
|
|
7255
|
+
return false;
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
async markUploaded(file, metadata) {
|
|
7259
|
+
const stateFile = this.uploadStatePath(file);
|
|
7260
|
+
await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
|
|
7261
|
+
await writeFile2(stateFile, `${JSON.stringify({
|
|
7262
|
+
file: path14.basename(file),
|
|
7263
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7264
|
+
...metadata
|
|
7265
|
+
}, null, 2)}
|
|
7266
|
+
`, { mode: 384 });
|
|
7267
|
+
}
|
|
7268
|
+
};
|
|
7269
|
+
function sha256Hex(body) {
|
|
7270
|
+
return createHash2("sha256").update(body).digest("hex");
|
|
7271
|
+
}
|
|
7272
|
+
function readPositiveIntegerEnv2(name, fallback) {
|
|
7273
|
+
const value = process.env[name];
|
|
7274
|
+
if (!value) return fallback;
|
|
7275
|
+
const parsed = Number(value);
|
|
7276
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
7277
|
+
}
|
|
7278
|
+
|
|
5818
7279
|
// src/core.ts
|
|
5819
7280
|
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
|
|
5820
7281
|
function parseDaemonCliArgs(args) {
|
|
@@ -5836,59 +7297,110 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
5836
7297
|
}
|
|
5837
7298
|
}
|
|
5838
7299
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
5839
|
-
const dirname =
|
|
5840
|
-
const jsPath =
|
|
7300
|
+
const dirname = path15.dirname(fileURLToPath(moduleUrl));
|
|
7301
|
+
const jsPath = path15.resolve(dirname, "chat-bridge.js");
|
|
5841
7302
|
try {
|
|
5842
7303
|
accessSync(jsPath);
|
|
5843
7304
|
return jsPath;
|
|
5844
7305
|
} catch {
|
|
5845
|
-
return
|
|
7306
|
+
return path15.resolve(dirname, "chat-bridge.ts");
|
|
5846
7307
|
}
|
|
5847
7308
|
}
|
|
5848
7309
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
5849
|
-
const thisDir =
|
|
5850
|
-
const bundledDistPath =
|
|
7310
|
+
const thisDir = path15.dirname(fileURLToPath(moduleUrl));
|
|
7311
|
+
const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
|
|
5851
7312
|
try {
|
|
5852
7313
|
accessSync(bundledDistPath);
|
|
5853
7314
|
return bundledDistPath;
|
|
5854
7315
|
} catch {
|
|
5855
|
-
const workspaceDistPath =
|
|
7316
|
+
const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
5856
7317
|
accessSync(workspaceDistPath);
|
|
5857
7318
|
return workspaceDistPath;
|
|
5858
7319
|
}
|
|
5859
7320
|
}
|
|
5860
|
-
function detectRuntimes() {
|
|
7321
|
+
function detectRuntimes(tracer = noopTracer) {
|
|
5861
7322
|
const ids = [];
|
|
5862
7323
|
const versions = {};
|
|
7324
|
+
const span = tracer.startSpan("daemon.runtime.detect", {
|
|
7325
|
+
surface: "daemon",
|
|
7326
|
+
kind: "internal",
|
|
7327
|
+
attrs: {
|
|
7328
|
+
known_runtime_count: RUNTIMES.length
|
|
7329
|
+
}
|
|
7330
|
+
});
|
|
5863
7331
|
for (const runtime of RUNTIMES) {
|
|
5864
7332
|
const driver = getDriver(runtime.id);
|
|
7333
|
+
let probeErrorPresent = false;
|
|
5865
7334
|
try {
|
|
5866
7335
|
if (driver.probe) {
|
|
5867
7336
|
const probe = driver.probe();
|
|
5868
7337
|
if (!probe.available) {
|
|
5869
7338
|
if (probe.version) versions[runtime.id] = probe.version;
|
|
7339
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7340
|
+
runtime: runtime.id,
|
|
7341
|
+
outcome: "unavailable",
|
|
7342
|
+
version_present: Boolean(probe.version),
|
|
7343
|
+
binary_path_present: false
|
|
7344
|
+
});
|
|
5870
7345
|
continue;
|
|
5871
7346
|
}
|
|
5872
7347
|
ids.push(runtime.id);
|
|
5873
7348
|
if (probe.version) versions[runtime.id] = probe.version;
|
|
7349
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7350
|
+
runtime: runtime.id,
|
|
7351
|
+
outcome: "available",
|
|
7352
|
+
version_present: Boolean(probe.version),
|
|
7353
|
+
binary_path_present: false
|
|
7354
|
+
});
|
|
5874
7355
|
continue;
|
|
5875
7356
|
}
|
|
5876
7357
|
} catch {
|
|
7358
|
+
probeErrorPresent = true;
|
|
5877
7359
|
}
|
|
5878
7360
|
const detectionBinaries = [runtime.binary];
|
|
7361
|
+
let detectedByPath = false;
|
|
5879
7362
|
for (const binary of detectionBinaries) {
|
|
5880
7363
|
const resolved = resolveCommandOnPath(binary);
|
|
5881
7364
|
if (!resolved) continue;
|
|
5882
7365
|
ids.push(runtime.id);
|
|
7366
|
+
detectedByPath = true;
|
|
5883
7367
|
const version = readCommandVersion(binary);
|
|
5884
7368
|
if (version) {
|
|
5885
7369
|
versions[runtime.id] = version;
|
|
5886
7370
|
}
|
|
7371
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7372
|
+
runtime: runtime.id,
|
|
7373
|
+
outcome: "available",
|
|
7374
|
+
version_present: Boolean(version),
|
|
7375
|
+
binary_path_present: true,
|
|
7376
|
+
probe_error_present: probeErrorPresent
|
|
7377
|
+
});
|
|
5887
7378
|
break;
|
|
5888
7379
|
}
|
|
7380
|
+
if (!detectedByPath) {
|
|
7381
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7382
|
+
runtime: runtime.id,
|
|
7383
|
+
outcome: "unavailable",
|
|
7384
|
+
version_present: false,
|
|
7385
|
+
binary_path_present: false,
|
|
7386
|
+
probe_error_present: probeErrorPresent
|
|
7387
|
+
});
|
|
7388
|
+
}
|
|
5889
7389
|
}
|
|
7390
|
+
span.end("ok", {
|
|
7391
|
+
attrs: {
|
|
7392
|
+
detected_runtime_count: ids.length
|
|
7393
|
+
}
|
|
7394
|
+
});
|
|
5890
7395
|
return { ids, versions };
|
|
5891
7396
|
}
|
|
7397
|
+
function readPositiveIntegerEnv3(name, fallback) {
|
|
7398
|
+
const raw = process.env[name];
|
|
7399
|
+
if (!raw) return fallback;
|
|
7400
|
+
const parsed = Number(raw);
|
|
7401
|
+
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
7402
|
+
return Math.floor(parsed);
|
|
7403
|
+
}
|
|
5892
7404
|
function formatChannelTarget(msg) {
|
|
5893
7405
|
return msg.message.channel_type === "dm" ? `dm:@${msg.message.channel_name}` : `#${msg.message.channel_name}`;
|
|
5894
7406
|
}
|
|
@@ -5938,14 +7450,18 @@ var DaemonCore = class {
|
|
|
5938
7450
|
connection;
|
|
5939
7451
|
reminderCache;
|
|
5940
7452
|
tracer;
|
|
7453
|
+
injectedTracer;
|
|
5941
7454
|
machineLock = null;
|
|
7455
|
+
localTraceSink = null;
|
|
7456
|
+
traceBundleUploader = null;
|
|
5942
7457
|
constructor(options) {
|
|
5943
7458
|
this.options = options;
|
|
5944
7459
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
5945
7460
|
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
5946
7461
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
5947
|
-
this.
|
|
7462
|
+
this.injectedTracer = Boolean(options.tracer);
|
|
5948
7463
|
this.tracer = options.tracer ?? noopTracer;
|
|
7464
|
+
this.runtimeDetector = options.runtimeDetector ?? (() => detectRuntimes(this.tracer));
|
|
5949
7465
|
this.reminderCache = new ReminderCache({
|
|
5950
7466
|
clock: options.reminderClock,
|
|
5951
7467
|
onFire: (job) => this.onReminderFire(job)
|
|
@@ -5955,7 +7471,8 @@ var DaemonCore = class {
|
|
|
5955
7471
|
dataDir: options.dataDir,
|
|
5956
7472
|
serverUrl: options.serverUrl,
|
|
5957
7473
|
defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
|
|
5958
|
-
slockCliPath: this.slockCliPath
|
|
7474
|
+
slockCliPath: this.slockCliPath,
|
|
7475
|
+
tracer: this.tracer
|
|
5959
7476
|
};
|
|
5960
7477
|
this.agentManager = options.agentManagerFactory ? options.agentManagerFactory(this.chatBridgePath, (msg) => connection.send(msg), options.apiKey, agentManagerOptions) : new AgentProcessManager(this.chatBridgePath, (msg) => connection.send(msg), options.apiKey, agentManagerOptions);
|
|
5961
7478
|
const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
|
|
@@ -5965,15 +7482,48 @@ var DaemonCore = class {
|
|
|
5965
7482
|
...options.connectionOptions,
|
|
5966
7483
|
onMessage: (msg) => this.handleMessage(msg),
|
|
5967
7484
|
onConnect: () => this.handleConnect(),
|
|
5968
|
-
onDisconnect: () => this.handleDisconnect()
|
|
7485
|
+
onDisconnect: () => this.handleDisconnect(),
|
|
7486
|
+
onTraceEvent: (name, attrs, status) => this.recordDaemonTrace(name, attrs, status)
|
|
5969
7487
|
});
|
|
5970
7488
|
this.connection = connection;
|
|
5971
7489
|
}
|
|
5972
7490
|
resolveMachineStateRoot() {
|
|
5973
7491
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
5974
|
-
if (this.options.dataDir) return
|
|
7492
|
+
if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
|
|
5975
7493
|
return DEFAULT_MACHINE_STATE_ROOT;
|
|
5976
7494
|
}
|
|
7495
|
+
shouldEnableLocalTrace() {
|
|
7496
|
+
if (this.injectedTracer) return false;
|
|
7497
|
+
if (!this.options.localTrace) return false;
|
|
7498
|
+
return process.env.SLOCK_DAEMON_LOCAL_TRACE !== "0";
|
|
7499
|
+
}
|
|
7500
|
+
installLocalTraceSink(machineDir) {
|
|
7501
|
+
if (!this.shouldEnableLocalTrace()) return;
|
|
7502
|
+
this.localTraceSink = new LocalRotatingTraceSink({
|
|
7503
|
+
machineDir,
|
|
7504
|
+
maxFileBytes: this.options.localTraceMaxFileBytes ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_BYTES", 5 * 1024 * 1024),
|
|
7505
|
+
maxFiles: this.options.localTraceMaxFiles ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILES", 8)
|
|
7506
|
+
});
|
|
7507
|
+
this.tracer = new BasicTracer({
|
|
7508
|
+
sink: this.localTraceSink
|
|
7509
|
+
});
|
|
7510
|
+
this.agentManager.setTracer(this.tracer);
|
|
7511
|
+
}
|
|
7512
|
+
installTraceBundleUploader(machineDir) {
|
|
7513
|
+
if (!this.shouldEnableLocalTrace()) return;
|
|
7514
|
+
if (this.traceBundleUploader) return;
|
|
7515
|
+
const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL;
|
|
7516
|
+
if (!workerUrl) return;
|
|
7517
|
+
this.traceBundleUploader = new DaemonTraceBundleUploader({
|
|
7518
|
+
machineDir,
|
|
7519
|
+
serverUrl: this.options.serverUrl,
|
|
7520
|
+
apiKey: this.options.apiKey,
|
|
7521
|
+
workerUrl,
|
|
7522
|
+
tracer: this.tracer,
|
|
7523
|
+
currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null
|
|
7524
|
+
});
|
|
7525
|
+
this.traceBundleUploader.start();
|
|
7526
|
+
}
|
|
5977
7527
|
start() {
|
|
5978
7528
|
logger.info("[Slock Daemon] Starting...");
|
|
5979
7529
|
if (!this.machineLock) {
|
|
@@ -5983,10 +7533,24 @@ var DaemonCore = class {
|
|
|
5983
7533
|
rootDir: this.resolveMachineStateRoot()
|
|
5984
7534
|
});
|
|
5985
7535
|
logger.info(`[Slock Daemon] Acquired machine lock: ${this.machineLock.lockDir}`);
|
|
7536
|
+
this.installLocalTraceSink(this.machineLock.machineDir);
|
|
7537
|
+
this.installTraceBundleUploader(this.machineLock.machineDir);
|
|
7538
|
+
const span = this.tracer.startSpan("daemon.lifecycle.start", {
|
|
7539
|
+
surface: "daemon",
|
|
7540
|
+
kind: "internal",
|
|
7541
|
+
attrs: {
|
|
7542
|
+
machine_dir_present: true,
|
|
7543
|
+
local_trace_enabled: this.shouldEnableLocalTrace()
|
|
7544
|
+
}
|
|
7545
|
+
});
|
|
7546
|
+
span.addEvent("daemon.machine_lock.acquired", { machine_dir_present: true });
|
|
7547
|
+
span.end("ok");
|
|
5986
7548
|
}
|
|
5987
7549
|
try {
|
|
5988
7550
|
this.connection.connect();
|
|
5989
7551
|
} catch (err) {
|
|
7552
|
+
this.traceBundleUploader?.stop();
|
|
7553
|
+
this.traceBundleUploader = null;
|
|
5990
7554
|
this.machineLock.release();
|
|
5991
7555
|
this.machineLock = null;
|
|
5992
7556
|
throw err;
|
|
@@ -5994,13 +7558,24 @@ var DaemonCore = class {
|
|
|
5994
7558
|
}
|
|
5995
7559
|
async stop() {
|
|
5996
7560
|
logger.info("[Slock Daemon] Shutting down...");
|
|
7561
|
+
const span = this.tracer.startSpan("daemon.lifecycle.stop", {
|
|
7562
|
+
surface: "daemon",
|
|
7563
|
+
kind: "internal",
|
|
7564
|
+
attrs: { machine_lock_present: Boolean(this.machineLock) }
|
|
7565
|
+
});
|
|
5997
7566
|
this.reminderCache.clear();
|
|
7567
|
+
this.traceBundleUploader?.stop();
|
|
7568
|
+
this.traceBundleUploader = null;
|
|
5998
7569
|
try {
|
|
5999
7570
|
await this.agentManager.stopAll();
|
|
7571
|
+
span.addEvent("daemon.agents.stopped");
|
|
6000
7572
|
} finally {
|
|
6001
7573
|
this.connection.disconnect();
|
|
7574
|
+
span.addEvent("daemon.connection.disconnect_requested");
|
|
6002
7575
|
this.machineLock?.release();
|
|
7576
|
+
if (this.machineLock) span.addEvent("daemon.machine_lock.released");
|
|
6003
7577
|
this.machineLock = null;
|
|
7578
|
+
span.end("ok");
|
|
6004
7579
|
}
|
|
6005
7580
|
}
|
|
6006
7581
|
get connected() {
|
|
@@ -6009,6 +7584,14 @@ var DaemonCore = class {
|
|
|
6009
7584
|
getRunningAgentIds() {
|
|
6010
7585
|
return this.agentManager.getRunningAgentIds();
|
|
6011
7586
|
}
|
|
7587
|
+
recordDaemonTrace(name, attrs, status = "ok") {
|
|
7588
|
+
const span = this.tracer.startSpan(name, {
|
|
7589
|
+
surface: "daemon",
|
|
7590
|
+
kind: "internal",
|
|
7591
|
+
attrs
|
|
7592
|
+
});
|
|
7593
|
+
span.end(status);
|
|
7594
|
+
}
|
|
6012
7595
|
handleMessage(msg) {
|
|
6013
7596
|
const summary = summarizeIncomingMessage(msg);
|
|
6014
7597
|
logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
|
|
@@ -6038,50 +7621,88 @@ var DaemonCore = class {
|
|
|
6038
7621
|
kind: "consumer",
|
|
6039
7622
|
attrs: {
|
|
6040
7623
|
agentId: msg.agentId,
|
|
7624
|
+
deliveryId: msg.deliveryId,
|
|
7625
|
+
delivery_correlation_id: msg.deliveryId ?? msg.message.message_id,
|
|
7626
|
+
messageId: msg.message.message_id,
|
|
7627
|
+
message_id_present: Boolean(msg.message.message_id),
|
|
6041
7628
|
seq: msg.seq
|
|
6042
7629
|
}
|
|
6043
7630
|
});
|
|
6044
7631
|
logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
|
|
6045
7632
|
try {
|
|
6046
|
-
span.addEvent("daemon.receive", { seq: msg.seq });
|
|
6047
|
-
this.agentManager.deliverMessage(msg.agentId, msg.message);
|
|
6048
|
-
span.addEvent("daemon.deliver_to_agent_manager");
|
|
7633
|
+
span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
|
|
7634
|
+
const accepted = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
|
|
7635
|
+
span.addEvent("daemon.deliver_to_agent_manager", { accepted });
|
|
7636
|
+
if (!accepted) {
|
|
7637
|
+
span.end("ok", { attrs: { outcome: "not-accepted" } });
|
|
7638
|
+
break;
|
|
7639
|
+
}
|
|
6049
7640
|
const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
|
|
6050
7641
|
span.addEvent("daemon.ack.sent", { seq: ackSeq });
|
|
6051
7642
|
this.connection.send({
|
|
6052
7643
|
type: "agent:deliver:ack",
|
|
6053
7644
|
agentId: msg.agentId,
|
|
6054
7645
|
seq: ackSeq,
|
|
6055
|
-
traceparent: formatTraceparent(span.context)
|
|
7646
|
+
traceparent: formatTraceparent(span.context),
|
|
7647
|
+
deliveryId: msg.deliveryId
|
|
6056
7648
|
});
|
|
6057
|
-
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq } });
|
|
7649
|
+
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
|
|
6058
7650
|
} catch (err) {
|
|
6059
|
-
span.end("error", { attrs: {
|
|
7651
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
6060
7652
|
throw err;
|
|
6061
7653
|
}
|
|
6062
7654
|
break;
|
|
6063
7655
|
}
|
|
6064
|
-
case "agent:runtime_profile:migration":
|
|
7656
|
+
case "agent:runtime_profile:migration": {
|
|
7657
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.received", {
|
|
7658
|
+
parent: parseTraceparent(msg.traceparent),
|
|
7659
|
+
surface: "daemon",
|
|
7660
|
+
kind: "consumer",
|
|
7661
|
+
attrs: {
|
|
7662
|
+
agentId: msg.agentId,
|
|
7663
|
+
control_kind: "migration",
|
|
7664
|
+
key_present: Boolean(msg.migrationKey),
|
|
7665
|
+
launchId: msg.launchId || void 0
|
|
7666
|
+
}
|
|
7667
|
+
});
|
|
6065
7668
|
logger.info(`[Agent ${msg.agentId}] Runtime profile migration received (${msg.migrationKey})`);
|
|
6066
|
-
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message);
|
|
7669
|
+
const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context));
|
|
7670
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
6067
7671
|
break;
|
|
6068
|
-
|
|
7672
|
+
}
|
|
7673
|
+
case "agent:runtime_profile:daemon_release_notice": {
|
|
7674
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.received", {
|
|
7675
|
+
parent: parseTraceparent(msg.traceparent),
|
|
7676
|
+
surface: "daemon",
|
|
7677
|
+
kind: "consumer",
|
|
7678
|
+
attrs: {
|
|
7679
|
+
agentId: msg.agentId,
|
|
7680
|
+
control_kind: "daemon_release_notice",
|
|
7681
|
+
key_present: Boolean(msg.noticeKey),
|
|
7682
|
+
launchId: msg.launchId || void 0
|
|
7683
|
+
}
|
|
7684
|
+
});
|
|
6069
7685
|
logger.info(`[Agent ${msg.agentId}] Runtime profile daemon release notice received (${msg.noticeKey})`);
|
|
6070
|
-
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message);
|
|
7686
|
+
const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context));
|
|
7687
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
6071
7688
|
break;
|
|
7689
|
+
}
|
|
6072
7690
|
case "agent:workspace:list":
|
|
6073
7691
|
this.agentManager.getFileTree(msg.agentId, msg.dirPath).then((files) => {
|
|
6074
7692
|
this.connection.send({ type: "agent:workspace:file_tree", agentId: msg.agentId, files, dirPath: msg.dirPath });
|
|
6075
7693
|
});
|
|
6076
7694
|
break;
|
|
6077
7695
|
case "agent:workspace:read":
|
|
6078
|
-
this.agentManager.readFile(msg.agentId, msg.path).then(({ content, binary }) => {
|
|
7696
|
+
this.agentManager.readFile(msg.agentId, msg.path).then(({ content, binary, size, mimeType, encoding }) => {
|
|
6079
7697
|
this.connection.send({
|
|
6080
7698
|
type: "agent:workspace:file_content",
|
|
6081
7699
|
agentId: msg.agentId,
|
|
6082
7700
|
requestId: msg.requestId,
|
|
6083
7701
|
content,
|
|
6084
|
-
binary
|
|
7702
|
+
binary,
|
|
7703
|
+
size,
|
|
7704
|
+
mimeType,
|
|
7705
|
+
encoding
|
|
6085
7706
|
});
|
|
6086
7707
|
}).catch(() => {
|
|
6087
7708
|
this.connection.send({
|
|
@@ -6089,7 +7710,8 @@ var DaemonCore = class {
|
|
|
6089
7710
|
agentId: msg.agentId,
|
|
6090
7711
|
requestId: msg.requestId,
|
|
6091
7712
|
content: null,
|
|
6092
|
-
binary: false
|
|
7713
|
+
binary: false,
|
|
7714
|
+
size: 0
|
|
6093
7715
|
});
|
|
6094
7716
|
});
|
|
6095
7717
|
break;
|
|
@@ -6165,35 +7787,59 @@ var DaemonCore = class {
|
|
|
6165
7787
|
const { ids: runtimes, versions: runtimeVersions } = this.runtimeDetector();
|
|
6166
7788
|
const runtimeInfo = runtimes.map((id) => runtimeVersions[id] ? `${id} (${runtimeVersions[id]})` : id);
|
|
6167
7789
|
logger.info(`[Daemon] Detected runtimes: ${runtimeInfo.join(", ") || "none"}`);
|
|
7790
|
+
const runningAgentIds = this.agentManager.getRunningAgentIds();
|
|
7791
|
+
const idleAgentSessions = this.agentManager.getIdleAgentSessionIds();
|
|
7792
|
+
const runtimeProfileReports = this.agentManager.getAgentRuntimeProfileReports();
|
|
6168
7793
|
this.connection.send({
|
|
6169
7794
|
type: "ready",
|
|
6170
7795
|
capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
|
|
6171
7796
|
runtimes,
|
|
6172
|
-
runningAgents:
|
|
6173
|
-
hostname: this.options.hostname ??
|
|
6174
|
-
os: this.options.osDescription ?? `${
|
|
7797
|
+
runningAgents: runningAgentIds,
|
|
7798
|
+
hostname: this.options.hostname ?? os7.hostname(),
|
|
7799
|
+
os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
|
|
6175
7800
|
daemonVersion: this.daemonVersion
|
|
6176
7801
|
});
|
|
6177
|
-
|
|
7802
|
+
this.recordDaemonTrace("daemon.ready.sent", {
|
|
7803
|
+
runtimes_count: runtimes.length,
|
|
7804
|
+
running_agents_count: runningAgentIds.length,
|
|
7805
|
+
idle_agents_count: idleAgentSessions.length,
|
|
7806
|
+
runtime_profile_reports_count: runtimeProfileReports.length,
|
|
7807
|
+
daemon_version_present: Boolean(this.daemonVersion)
|
|
7808
|
+
});
|
|
7809
|
+
for (const agentId of runningAgentIds) {
|
|
6178
7810
|
const sessionId = this.agentManager.getAgentSessionId(agentId);
|
|
6179
7811
|
const launchId = this.agentManager.getAgentLaunchId(agentId);
|
|
6180
7812
|
if (sessionId) {
|
|
6181
7813
|
this.connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
|
|
6182
7814
|
}
|
|
6183
7815
|
}
|
|
6184
|
-
for (const { agentId, sessionId, launchId } of
|
|
7816
|
+
for (const { agentId, sessionId, launchId } of idleAgentSessions) {
|
|
6185
7817
|
this.connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
|
|
6186
7818
|
}
|
|
6187
|
-
for (const report of
|
|
7819
|
+
for (const report of runtimeProfileReports) {
|
|
7820
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
7821
|
+
surface: "daemon",
|
|
7822
|
+
kind: "producer",
|
|
7823
|
+
attrs: {
|
|
7824
|
+
agentId: report.agentId,
|
|
7825
|
+
launchId: report.launchId || void 0,
|
|
7826
|
+
runtime: report.facts.runtime,
|
|
7827
|
+
model_present: Boolean(report.facts.model),
|
|
7828
|
+
session_ref_present: Boolean(report.facts.sessionRef),
|
|
7829
|
+
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
7830
|
+
}
|
|
7831
|
+
});
|
|
6188
7832
|
this.connection.send({
|
|
6189
7833
|
type: "agent:runtime_profile",
|
|
6190
7834
|
agentId: report.agentId,
|
|
6191
7835
|
facts: report.facts,
|
|
6192
|
-
launchId: report.launchId || void 0
|
|
7836
|
+
launchId: report.launchId || void 0,
|
|
7837
|
+
traceparent: formatTraceparent(span.context)
|
|
6193
7838
|
});
|
|
7839
|
+
span.end("ok");
|
|
6194
7840
|
}
|
|
6195
|
-
const agentsForSnapshot = new Set(
|
|
6196
|
-
for (const { agentId } of
|
|
7841
|
+
const agentsForSnapshot = new Set(runningAgentIds);
|
|
7842
|
+
for (const { agentId } of idleAgentSessions) {
|
|
6197
7843
|
agentsForSnapshot.add(agentId);
|
|
6198
7844
|
}
|
|
6199
7845
|
for (const agentId of agentsForSnapshot) {
|
|
@@ -6203,6 +7849,10 @@ var DaemonCore = class {
|
|
|
6203
7849
|
}
|
|
6204
7850
|
handleDisconnect() {
|
|
6205
7851
|
logger.warn("[Daemon] Lost connection \u2014 agents continue running locally");
|
|
7852
|
+
this.recordDaemonTrace("daemon.connection.local_disconnect_observed", {
|
|
7853
|
+
running_agents_count: this.agentManager.getRunningAgentIds().length,
|
|
7854
|
+
idle_agents_count: this.agentManager.getIdleAgentSessionIds().length
|
|
7855
|
+
}, "cancelled");
|
|
6206
7856
|
this.options.lifecycleHooks?.onDisconnect?.();
|
|
6207
7857
|
}
|
|
6208
7858
|
};
|