@slock-ai/daemon 0.44.2 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-bridge.js +21 -104
- package/dist/{chunk-NM2MFLQ7.js → chunk-Q4XUZB34.js} +1707 -267
- package/dist/{chunk-JG7ONJZ6.js → chunk-Z3PCMYZO.js} +101 -1
- package/dist/cli/index.js +65 -14
- 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
|
|
|
@@ -1301,6 +1363,7 @@ var CLAUDE_DESKTOP_CLI_RELATIVE_PATH = path3.join("Applications", "Claude Code U
|
|
|
1301
1363
|
var CLAUDE_DESKTOP_CLI_SYSTEM_PATH = "/Applications/Claude Code URL Handler.app/Contents/MacOS/claude";
|
|
1302
1364
|
var CLAUDE_SYSTEM_PROMPT_FILE = "claude-system-prompt.md";
|
|
1303
1365
|
var CLAUDE_MCP_CONFIG_FILE = "claude-mcp-config.json";
|
|
1366
|
+
var SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME = "chat";
|
|
1304
1367
|
var CLAUDE_DISALLOWED_TOOLS = [
|
|
1305
1368
|
"EnterPlanMode",
|
|
1306
1369
|
"ExitPlanMode",
|
|
@@ -1326,6 +1389,80 @@ function probeClaude(deps = {}) {
|
|
|
1326
1389
|
version: readCommandVersion(command, [], deps) ?? void 0
|
|
1327
1390
|
};
|
|
1328
1391
|
}
|
|
1392
|
+
function isRecord(value) {
|
|
1393
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1394
|
+
}
|
|
1395
|
+
function expandClaudeMcpConfigVariables(raw, vars) {
|
|
1396
|
+
let expanded = raw;
|
|
1397
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
1398
|
+
expanded = expanded.replaceAll(`\${${name}}`, value);
|
|
1399
|
+
}
|
|
1400
|
+
return expanded;
|
|
1401
|
+
}
|
|
1402
|
+
function readClaudeMcpServers(configPath, vars = {}) {
|
|
1403
|
+
try {
|
|
1404
|
+
const parsed = JSON.parse(
|
|
1405
|
+
expandClaudeMcpConfigVariables(readFileSync(configPath, "utf8"), vars)
|
|
1406
|
+
);
|
|
1407
|
+
if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
|
|
1408
|
+
return parsed.mcpServers;
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
logger.warn(
|
|
1411
|
+
`[Claude] failed to read MCP config ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1412
|
+
);
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
function resolveClaudeConfigDir(ctx, home) {
|
|
1417
|
+
const configured = ctx.config.envVars?.CLAUDE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR;
|
|
1418
|
+
return configured && path3.isAbsolute(configured) ? configured : path3.join(home, ".claude");
|
|
1419
|
+
}
|
|
1420
|
+
function collectClaudeMcpConfigFiles(ctx, home) {
|
|
1421
|
+
const files = [];
|
|
1422
|
+
const pushIfFile = (candidate) => {
|
|
1423
|
+
try {
|
|
1424
|
+
if (existsSync2(candidate) && statSync(candidate).isFile()) {
|
|
1425
|
+
files.push({ path: candidate });
|
|
1426
|
+
}
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
pushIfFile(path3.join(home, ".claude.json"));
|
|
1431
|
+
pushIfFile(path3.join(ctx.workingDirectory, ".mcp.json"));
|
|
1432
|
+
const pluginRoot = path3.join(resolveClaudeConfigDir(ctx, home), "plugins");
|
|
1433
|
+
try {
|
|
1434
|
+
for (const entry of readdirSync(pluginRoot)) {
|
|
1435
|
+
const pluginPath = path3.join(pluginRoot, entry);
|
|
1436
|
+
const configPath = path3.join(pluginPath, ".mcp.json");
|
|
1437
|
+
try {
|
|
1438
|
+
if (existsSync2(configPath) && statSync(configPath).isFile()) {
|
|
1439
|
+
files.push({
|
|
1440
|
+
path: configPath,
|
|
1441
|
+
vars: { CLAUDE_PLUGIN_ROOT: pluginPath }
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
} catch {
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
return files;
|
|
1450
|
+
}
|
|
1451
|
+
function buildClaudeUserMcpServers(ctx, home) {
|
|
1452
|
+
const servers = /* @__PURE__ */ Object.create(null);
|
|
1453
|
+
for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
|
|
1454
|
+
const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
|
|
1455
|
+
if (!mcpServers) continue;
|
|
1456
|
+
for (const [name, server] of Object.entries(mcpServers)) {
|
|
1457
|
+
if (!isRecord(server)) {
|
|
1458
|
+
logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
servers[name] = server;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return servers;
|
|
1465
|
+
}
|
|
1329
1466
|
var ClaudeDriver = class {
|
|
1330
1467
|
id = "claude";
|
|
1331
1468
|
lifecycle = {
|
|
@@ -1381,36 +1518,46 @@ var ClaudeDriver = class {
|
|
|
1381
1518
|
}
|
|
1382
1519
|
return args;
|
|
1383
1520
|
}
|
|
1384
|
-
|
|
1521
|
+
buildRuntimeActionsMcpServer(ctx) {
|
|
1385
1522
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
1386
1523
|
const command = isTsSource ? "npx" : "node";
|
|
1387
1524
|
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
1525
|
+
return {
|
|
1526
|
+
command,
|
|
1527
|
+
args: [
|
|
1528
|
+
...bridgeArgs,
|
|
1529
|
+
"--agent-id",
|
|
1530
|
+
ctx.agentId,
|
|
1531
|
+
"--server-url",
|
|
1532
|
+
ctx.config.serverUrl,
|
|
1533
|
+
"--auth-token",
|
|
1534
|
+
ctx.config.authToken || ctx.daemonApiKey,
|
|
1535
|
+
"--runtime",
|
|
1536
|
+
this.id,
|
|
1537
|
+
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
1538
|
+
"--runtime-actions-only"
|
|
1539
|
+
]
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
buildRuntimeActionsMcpConfig(ctx, home = os.homedir()) {
|
|
1543
|
+
const userMcpServers = buildClaudeUserMcpServers(ctx, home);
|
|
1544
|
+
if (Object.prototype.hasOwnProperty.call(userMcpServers, SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME)) {
|
|
1545
|
+
logger.warn(
|
|
1546
|
+
`[Agent ${ctx.agentId}] Claude user MCP server "${SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME}" is reserved by Slock runtime actions and will be ignored`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1388
1549
|
return JSON.stringify({
|
|
1389
1550
|
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
|
-
}
|
|
1551
|
+
...userMcpServers,
|
|
1552
|
+
[SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME]: this.buildRuntimeActionsMcpServer(ctx)
|
|
1406
1553
|
}
|
|
1407
1554
|
});
|
|
1408
1555
|
}
|
|
1409
|
-
writeClaudeLaunchFiles(ctx, slockDir) {
|
|
1556
|
+
writeClaudeLaunchFiles(ctx, slockDir, home = os.homedir()) {
|
|
1410
1557
|
const systemPromptPath = path3.join(slockDir, CLAUDE_SYSTEM_PROMPT_FILE);
|
|
1411
1558
|
const mcpConfigPath = path3.join(slockDir, CLAUDE_MCP_CONFIG_FILE);
|
|
1412
1559
|
writeFileSync2(systemPromptPath, ctx.standingPrompt, { mode: 384 });
|
|
1413
|
-
writeFileSync2(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx), { mode: 384 });
|
|
1560
|
+
writeFileSync2(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx, home), { mode: 384 });
|
|
1414
1561
|
return { systemPromptPath, mcpConfigPath };
|
|
1415
1562
|
}
|
|
1416
1563
|
spawn(ctx) {
|
|
@@ -1558,8 +1705,8 @@ var ClaudeDriver = class {
|
|
|
1558
1705
|
|
|
1559
1706
|
// src/drivers/codex.ts
|
|
1560
1707
|
import { spawn as spawn2, execSync } from "child_process";
|
|
1561
|
-
import { existsSync as
|
|
1562
|
-
import
|
|
1708
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1709
|
+
import os2 from "os";
|
|
1563
1710
|
import path4 from "path";
|
|
1564
1711
|
function getCodexNotificationErrorMessage(params) {
|
|
1565
1712
|
const topLevelMessage = params?.message;
|
|
@@ -1573,7 +1720,7 @@ function getCodexNotificationErrorMessage(params) {
|
|
|
1573
1720
|
return null;
|
|
1574
1721
|
}
|
|
1575
1722
|
function ensureGitRepoForCodex(workingDirectory, deps = {}) {
|
|
1576
|
-
const existsSyncFn = deps.existsSyncFn ??
|
|
1723
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync3;
|
|
1577
1724
|
const execSyncFn = deps.execSyncFn ?? execSync;
|
|
1578
1725
|
const gitDir = path4.join(workingDirectory, ".git");
|
|
1579
1726
|
if (existsSyncFn(gitDir)) return;
|
|
@@ -1620,14 +1767,14 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1620
1767
|
try {
|
|
1621
1768
|
const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1622
1769
|
const candidate = path4.join(globalRoot, "@openai", "codex", "bin", "codex.js");
|
|
1623
|
-
if (
|
|
1770
|
+
if (existsSync3(candidate)) codexEntry = candidate;
|
|
1624
1771
|
} catch {
|
|
1625
1772
|
}
|
|
1626
1773
|
if (!codexEntry) {
|
|
1627
1774
|
try {
|
|
1628
1775
|
const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
|
|
1629
1776
|
const candidate = path4.join(path4.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
1630
|
-
if (
|
|
1777
|
+
if (existsSync3(candidate)) codexEntry = candidate;
|
|
1631
1778
|
} catch {
|
|
1632
1779
|
}
|
|
1633
1780
|
}
|
|
@@ -2047,12 +2194,12 @@ var CodexDriver = class {
|
|
|
2047
2194
|
return detectCodexModels();
|
|
2048
2195
|
}
|
|
2049
2196
|
};
|
|
2050
|
-
function detectCodexModels(home =
|
|
2197
|
+
function detectCodexModels(home = os2.homedir()) {
|
|
2051
2198
|
const cachePath = path4.join(home, ".codex", "models_cache.json");
|
|
2052
2199
|
const configPath = path4.join(home, ".codex", "config.toml");
|
|
2053
2200
|
let models = [];
|
|
2054
2201
|
try {
|
|
2055
|
-
const raw =
|
|
2202
|
+
const raw = readFileSync2(cachePath, "utf8");
|
|
2056
2203
|
const parsed = JSON.parse(raw);
|
|
2057
2204
|
const entries = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
2058
2205
|
for (const entry of entries) {
|
|
@@ -2069,7 +2216,7 @@ function detectCodexModels(home = os.homedir()) {
|
|
|
2069
2216
|
if (models.length === 0) return null;
|
|
2070
2217
|
let defaultModel;
|
|
2071
2218
|
try {
|
|
2072
|
-
const raw =
|
|
2219
|
+
const raw = readFileSync2(configPath, "utf8");
|
|
2073
2220
|
const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
|
|
2074
2221
|
if (match) defaultModel = match[1];
|
|
2075
2222
|
} catch {
|
|
@@ -2234,7 +2381,7 @@ var CopilotDriver = class {
|
|
|
2234
2381
|
|
|
2235
2382
|
// src/drivers/cursor.ts
|
|
2236
2383
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2237
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as
|
|
2384
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
2238
2385
|
import path6 from "path";
|
|
2239
2386
|
var CursorDriver = class {
|
|
2240
2387
|
id = "cursor";
|
|
@@ -2260,7 +2407,7 @@ var CursorDriver = class {
|
|
|
2260
2407
|
busyDeliveryMode = "none";
|
|
2261
2408
|
spawn(ctx) {
|
|
2262
2409
|
const cursorDir = path6.join(ctx.workingDirectory, ".cursor");
|
|
2263
|
-
if (!
|
|
2410
|
+
if (!existsSync4(cursorDir)) {
|
|
2264
2411
|
mkdirSync2(cursorDir, { recursive: true });
|
|
2265
2412
|
}
|
|
2266
2413
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
@@ -2412,18 +2559,14 @@ function runCursorModelsCommand() {
|
|
|
2412
2559
|
|
|
2413
2560
|
// src/drivers/gemini.ts
|
|
2414
2561
|
import { spawn as spawn5 } from "child_process";
|
|
2415
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3
|
|
2562
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
2416
2563
|
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
|
-
};
|
|
2564
|
+
function buildGeminiSpawnEnv(ctx, platform = process.platform) {
|
|
2565
|
+
const { spawnEnv } = prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
|
|
2566
|
+
if (!Object.prototype.hasOwnProperty.call(ctx.config.envVars ?? {}, "GEMINI_CLI_TRUST_WORKSPACE")) {
|
|
2567
|
+
spawnEnv.GEMINI_CLI_TRUST_WORKSPACE = "true";
|
|
2568
|
+
}
|
|
2569
|
+
return spawnEnv;
|
|
2427
2570
|
}
|
|
2428
2571
|
var GeminiDriver = class {
|
|
2429
2572
|
id = "gemini";
|
|
@@ -2434,40 +2577,26 @@ var GeminiDriver = class {
|
|
|
2434
2577
|
inFlightWake: "spawn_new"
|
|
2435
2578
|
};
|
|
2436
2579
|
communication = {
|
|
2437
|
-
chat: "
|
|
2580
|
+
chat: "slock_cli",
|
|
2438
2581
|
runtimeControl: "mcp_runtime_actions"
|
|
2439
2582
|
};
|
|
2440
2583
|
session = {
|
|
2441
2584
|
recovery: "resume_or_fresh"
|
|
2442
2585
|
};
|
|
2443
2586
|
model = {
|
|
2444
|
-
detectedModelsVerifiedAs: "
|
|
2445
|
-
toLaunchSpec: (modelId) =>
|
|
2587
|
+
detectedModelsVerifiedAs: "suggestion_only",
|
|
2588
|
+
toLaunchSpec: (modelId) => modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] }
|
|
2446
2589
|
};
|
|
2447
2590
|
supportsStdinNotification = false;
|
|
2448
2591
|
mcpToolPrefix = "";
|
|
2449
2592
|
busyDeliveryMode = "none";
|
|
2593
|
+
usesSlockCliForCommunication = true;
|
|
2450
2594
|
sessionId = null;
|
|
2451
2595
|
sessionAnnounced = false;
|
|
2452
2596
|
spawn(ctx) {
|
|
2453
2597
|
this.sessionId = ctx.config.sessionId || null;
|
|
2454
2598
|
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");
|
|
2599
|
+
this.writeGeminiSettings(ctx);
|
|
2471
2600
|
const args = [
|
|
2472
2601
|
"--output-format",
|
|
2473
2602
|
"stream-json",
|
|
@@ -2541,23 +2670,56 @@ var GeminiDriver = class {
|
|
|
2541
2670
|
return null;
|
|
2542
2671
|
}
|
|
2543
2672
|
buildSystemPrompt(config, _agentId) {
|
|
2544
|
-
return
|
|
2673
|
+
return buildCliTransportSystemPrompt(config, {
|
|
2545
2674
|
toolPrefix: "",
|
|
2546
2675
|
extraCriticalRules: [
|
|
2547
|
-
"-
|
|
2676
|
+
"- 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."
|
|
2677
|
+
],
|
|
2678
|
+
postStartupNotes: [
|
|
2679
|
+
"**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
2680
|
],
|
|
2549
|
-
postStartupNotes: [],
|
|
2550
2681
|
includeStdinNotificationSection: false,
|
|
2551
2682
|
messageNotificationStyle: "poll"
|
|
2552
2683
|
});
|
|
2553
2684
|
}
|
|
2685
|
+
writeGeminiSettings(ctx) {
|
|
2686
|
+
const geminiDir = path7.join(ctx.workingDirectory, ".gemini");
|
|
2687
|
+
mkdirSync3(geminiDir, { recursive: true });
|
|
2688
|
+
const settingsPath = path7.join(geminiDir, "settings.json");
|
|
2689
|
+
writeFileSync5(settingsPath, JSON.stringify(this.buildRuntimeActionsMcpSettings(ctx)), "utf8");
|
|
2690
|
+
}
|
|
2691
|
+
buildRuntimeActionsMcpSettings(ctx) {
|
|
2692
|
+
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
2693
|
+
const command = isTsSource ? "npx" : "node";
|
|
2694
|
+
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
2695
|
+
return {
|
|
2696
|
+
mcpServers: {
|
|
2697
|
+
chat: {
|
|
2698
|
+
command,
|
|
2699
|
+
args: [
|
|
2700
|
+
...bridgeArgs,
|
|
2701
|
+
"--agent-id",
|
|
2702
|
+
ctx.agentId,
|
|
2703
|
+
"--server-url",
|
|
2704
|
+
ctx.config.serverUrl,
|
|
2705
|
+
"--auth-token",
|
|
2706
|
+
ctx.config.authToken || ctx.daemonApiKey,
|
|
2707
|
+
"--runtime",
|
|
2708
|
+
this.id,
|
|
2709
|
+
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
2710
|
+
"--runtime-actions-only"
|
|
2711
|
+
]
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2554
2716
|
};
|
|
2555
2717
|
|
|
2556
2718
|
// src/drivers/kimi.ts
|
|
2557
2719
|
import { randomUUID } from "crypto";
|
|
2558
2720
|
import { spawn as spawn6 } from "child_process";
|
|
2559
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2560
|
-
import
|
|
2721
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2722
|
+
import os3 from "os";
|
|
2561
2723
|
import path8 from "path";
|
|
2562
2724
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
2563
2725
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
@@ -2773,11 +2935,11 @@ var KimiDriver = class {
|
|
|
2773
2935
|
return detectKimiModels();
|
|
2774
2936
|
}
|
|
2775
2937
|
};
|
|
2776
|
-
function detectKimiModels(home =
|
|
2938
|
+
function detectKimiModels(home = os3.homedir()) {
|
|
2777
2939
|
const configPath = path8.join(home, ".kimi", "config.toml");
|
|
2778
2940
|
let raw;
|
|
2779
2941
|
try {
|
|
2780
|
-
raw =
|
|
2942
|
+
raw = readFileSync3(configPath, "utf8");
|
|
2781
2943
|
} catch {
|
|
2782
2944
|
return null;
|
|
2783
2945
|
}
|
|
@@ -2800,9 +2962,9 @@ function detectKimiModels(home = os2.homedir()) {
|
|
|
2800
2962
|
}
|
|
2801
2963
|
|
|
2802
2964
|
// src/drivers/opencode.ts
|
|
2803
|
-
import { spawn as spawn7 } from "child_process";
|
|
2804
|
-
import { readFileSync as
|
|
2805
|
-
import
|
|
2965
|
+
import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
|
|
2966
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2967
|
+
import os4 from "os";
|
|
2806
2968
|
import path9 from "path";
|
|
2807
2969
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
2808
2970
|
var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
|
|
@@ -2842,10 +3004,10 @@ function parseUserOpenCodeConfig(ctx) {
|
|
|
2842
3004
|
const raw = ctx.config.envVars?.OPENCODE_CONFIG_CONTENT;
|
|
2843
3005
|
return parseOpenCodeConfigContent(raw);
|
|
2844
3006
|
}
|
|
2845
|
-
function readLocalOpenCodeConfig(home =
|
|
3007
|
+
function readLocalOpenCodeConfig(home = os4.homedir()) {
|
|
2846
3008
|
const configPath = path9.join(home, ".config", "opencode", "opencode.json");
|
|
2847
3009
|
try {
|
|
2848
|
-
return parseOpenCodeConfigContent(
|
|
3010
|
+
return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
|
|
2849
3011
|
} catch {
|
|
2850
3012
|
}
|
|
2851
3013
|
return {};
|
|
@@ -2891,7 +3053,7 @@ function mergeOpenCodeConfigs(localConfig, envConfig) {
|
|
|
2891
3053
|
}
|
|
2892
3054
|
};
|
|
2893
3055
|
}
|
|
2894
|
-
function buildOpenCodeConfig(ctx, home =
|
|
3056
|
+
function buildOpenCodeConfig(ctx, home = os4.homedir()) {
|
|
2895
3057
|
const userConfig = mergeOpenCodeConfigs(readLocalOpenCodeConfig(home), parseUserOpenCodeConfig(ctx));
|
|
2896
3058
|
const userAgents = recordField(userConfig.agent);
|
|
2897
3059
|
const userSlockAgent = recordField(userAgents[SLOCK_AGENT_NAME]);
|
|
@@ -2916,7 +3078,7 @@ function buildOpenCodeConfig(ctx, home = os3.homedir()) {
|
|
|
2916
3078
|
}
|
|
2917
3079
|
};
|
|
2918
3080
|
}
|
|
2919
|
-
function buildOpenCodeLaunchOptions(ctx, home =
|
|
3081
|
+
function buildOpenCodeLaunchOptions(ctx, home = os4.homedir()) {
|
|
2920
3082
|
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
2921
3083
|
const config = buildOpenCodeConfig(ctx, home);
|
|
2922
3084
|
const env = {
|
|
@@ -2943,24 +3105,42 @@ function buildOpenCodeLaunchOptions(ctx, home = os3.homedir()) {
|
|
|
2943
3105
|
args.push("--", turnPrompt);
|
|
2944
3106
|
return { args, env, config };
|
|
2945
3107
|
}
|
|
2946
|
-
function
|
|
2947
|
-
const
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3108
|
+
function parseOpenCodeModelsOutput(output) {
|
|
3109
|
+
const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
3110
|
+
const models = [];
|
|
3111
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3112
|
+
for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
|
|
3113
|
+
const line = rawLine.trim();
|
|
3114
|
+
if (!line || line.startsWith("{") || line.startsWith("}") || line.startsWith('"')) continue;
|
|
3115
|
+
if (/^opencode models\b/i.test(line) || /^list all available models$/i.test(line)) continue;
|
|
3116
|
+
if (!line.includes("/") || /\s/.test(line) || line.startsWith("-")) continue;
|
|
3117
|
+
if (seen.has(line)) continue;
|
|
3118
|
+
seen.add(line);
|
|
3119
|
+
models.push({
|
|
3120
|
+
id: line,
|
|
3121
|
+
label: line,
|
|
3122
|
+
verified: "launchable"
|
|
3123
|
+
});
|
|
2961
3124
|
}
|
|
2962
3125
|
return models.length > 0 ? { models } : null;
|
|
2963
3126
|
}
|
|
3127
|
+
function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeModelsCommand) {
|
|
3128
|
+
const commandResult = runCommand(home);
|
|
3129
|
+
if (commandResult.error || commandResult.status !== 0) return null;
|
|
3130
|
+
return parseOpenCodeModelsOutput(commandResult.stdout);
|
|
3131
|
+
}
|
|
3132
|
+
function runOpenCodeModelsCommand(home) {
|
|
3133
|
+
const result = spawnSync2("opencode", ["models"], {
|
|
3134
|
+
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3135
|
+
encoding: "utf8",
|
|
3136
|
+
timeout: 5e3
|
|
3137
|
+
});
|
|
3138
|
+
return {
|
|
3139
|
+
status: result.status,
|
|
3140
|
+
stdout: String(result.stdout || ""),
|
|
3141
|
+
error: result.error
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
2964
3144
|
function isSystemFirstMessageTask(message) {
|
|
2965
3145
|
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
|
|
2966
3146
|
}
|
|
@@ -3236,9 +3416,39 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
|
|
|
3236
3416
|
}
|
|
3237
3417
|
|
|
3238
3418
|
// src/agentProcessManager.ts
|
|
3239
|
-
var DATA_DIR = path11.join(
|
|
3240
|
-
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS =
|
|
3419
|
+
var DATA_DIR = path11.join(os5.homedir(), ".slock", "agents");
|
|
3420
|
+
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
3241
3421
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
3422
|
+
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
3423
|
+
var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
3424
|
+
var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3425
|
+
".md",
|
|
3426
|
+
".txt",
|
|
3427
|
+
".json",
|
|
3428
|
+
".js",
|
|
3429
|
+
".ts",
|
|
3430
|
+
".jsx",
|
|
3431
|
+
".tsx",
|
|
3432
|
+
".yaml",
|
|
3433
|
+
".yml",
|
|
3434
|
+
".toml",
|
|
3435
|
+
".log",
|
|
3436
|
+
".csv",
|
|
3437
|
+
".xml",
|
|
3438
|
+
".html",
|
|
3439
|
+
".css",
|
|
3440
|
+
".sh",
|
|
3441
|
+
".py"
|
|
3442
|
+
]);
|
|
3443
|
+
var WORKSPACE_IMAGE_MIME_BY_EXTENSION = {
|
|
3444
|
+
".apng": "image/apng",
|
|
3445
|
+
".avif": "image/avif",
|
|
3446
|
+
".gif": "image/gif",
|
|
3447
|
+
".jpg": "image/jpeg",
|
|
3448
|
+
".jpeg": "image/jpeg",
|
|
3449
|
+
".png": "image/png",
|
|
3450
|
+
".webp": "image/webp"
|
|
3451
|
+
};
|
|
3242
3452
|
function readPositiveIntegerEnv(name, fallback) {
|
|
3243
3453
|
const raw = process.env[name];
|
|
3244
3454
|
if (!raw) return fallback;
|
|
@@ -3278,6 +3488,7 @@ function formatMessageTarget(message) {
|
|
|
3278
3488
|
function getMessageShortId(messageId) {
|
|
3279
3489
|
return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
|
|
3280
3490
|
}
|
|
3491
|
+
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
3492
|
function findSessionJsonl(root, predicate) {
|
|
3282
3493
|
let visited = 0;
|
|
3283
3494
|
const maxEntries = 1e4;
|
|
@@ -3286,7 +3497,7 @@ function findSessionJsonl(root, predicate) {
|
|
|
3286
3497
|
if (depth < 0 || visited >= maxEntries) return null;
|
|
3287
3498
|
let entries;
|
|
3288
3499
|
try {
|
|
3289
|
-
entries =
|
|
3500
|
+
entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
|
|
3290
3501
|
} catch {
|
|
3291
3502
|
return null;
|
|
3292
3503
|
}
|
|
@@ -3332,11 +3543,11 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
3332
3543
|
return null;
|
|
3333
3544
|
}
|
|
3334
3545
|
}
|
|
3335
|
-
function resolveRuntimeSessionRef(runtime, sessionId, homeDir =
|
|
3546
|
+
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
3336
3547
|
const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
|
|
3337
3548
|
if (directPath) {
|
|
3338
3549
|
try {
|
|
3339
|
-
if (
|
|
3550
|
+
if (statSync2(directPath).isFile()) {
|
|
3340
3551
|
return { label: sessionId, path: directPath, runtime, reachable: true };
|
|
3341
3552
|
}
|
|
3342
3553
|
} catch {
|
|
@@ -3432,6 +3643,16 @@ function formatRuntimeProfileControlPrompt(messages) {
|
|
|
3432
3643
|
return null;
|
|
3433
3644
|
}
|
|
3434
3645
|
const body = controls.map(({ message }) => message.content).join("\n\n---\n\n");
|
|
3646
|
+
const hasMigration = controls.some(({ notification }) => notification?.kind === "migration");
|
|
3647
|
+
if (!hasMigration) {
|
|
3648
|
+
return [
|
|
3649
|
+
"Runtime Profile daemon release notice.",
|
|
3650
|
+
"",
|
|
3651
|
+
"Read the notice below before continuing. No chat reply or runtime control action is required for this notice \u2014 resume normal inbox processing afterward.",
|
|
3652
|
+
"",
|
|
3653
|
+
body
|
|
3654
|
+
].join("\n");
|
|
3655
|
+
}
|
|
3435
3656
|
return [
|
|
3436
3657
|
"Runtime Profile control notice.",
|
|
3437
3658
|
"",
|
|
@@ -3937,6 +4158,15 @@ function classifyTerminalFailure(ap) {
|
|
|
3937
4158
|
}
|
|
3938
4159
|
return null;
|
|
3939
4160
|
}
|
|
4161
|
+
function hasDirectStdinRecoveryEvidence(ap) {
|
|
4162
|
+
const candidates = [
|
|
4163
|
+
ap.lastRuntimeError,
|
|
4164
|
+
...ap.recentStderr
|
|
4165
|
+
].filter((value) => !!value);
|
|
4166
|
+
return candidates.some(
|
|
4167
|
+
(text) => /write_stdin failed|stdin is closed|closed for this session|session.*closed/i.test(text)
|
|
4168
|
+
);
|
|
4169
|
+
}
|
|
3940
4170
|
function isMissingResumeSession(ap) {
|
|
3941
4171
|
if (!ap.sessionId) return false;
|
|
3942
4172
|
const candidates = [
|
|
@@ -3978,7 +4208,6 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3978
4208
|
// Prevent concurrent starts of same agent
|
|
3979
4209
|
queuedAgentStarts = /* @__PURE__ */ new Map();
|
|
3980
4210
|
agentStartQueue = [];
|
|
3981
|
-
activeAgentStartPermits = /* @__PURE__ */ new Set();
|
|
3982
4211
|
activeAgentStartCount = 0;
|
|
3983
4212
|
agentStartPumpTimer = null;
|
|
3984
4213
|
lastAgentStartAt = 0;
|
|
@@ -3998,6 +4227,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3998
4227
|
driverResolver;
|
|
3999
4228
|
defaultAgentEnvVarsProvider;
|
|
4000
4229
|
tracer;
|
|
4230
|
+
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
4001
4231
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
4002
4232
|
this.chatBridgePath = chatBridgePath;
|
|
4003
4233
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
@@ -4005,7 +4235,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4005
4235
|
this.daemonApiKey = daemonApiKey;
|
|
4006
4236
|
this.serverUrl = opts.serverUrl;
|
|
4007
4237
|
this.dataDir = opts.dataDir || DATA_DIR;
|
|
4008
|
-
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir ||
|
|
4238
|
+
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
|
|
4009
4239
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
4010
4240
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
4011
4241
|
this.tracer = opts.tracer ?? noopTracer;
|
|
@@ -4022,16 +4252,75 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4022
4252
|
)
|
|
4023
4253
|
);
|
|
4024
4254
|
}
|
|
4255
|
+
setTracer(tracer) {
|
|
4256
|
+
this.tracer = tracer;
|
|
4257
|
+
}
|
|
4258
|
+
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
4259
|
+
const span = this.tracer.startSpan(name, {
|
|
4260
|
+
parent: parseTraceparent(parentTraceparent),
|
|
4261
|
+
surface: "daemon",
|
|
4262
|
+
kind: "internal",
|
|
4263
|
+
attrs
|
|
4264
|
+
});
|
|
4265
|
+
span.end(status);
|
|
4266
|
+
}
|
|
4267
|
+
startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4268
|
+
return {
|
|
4269
|
+
agentId,
|
|
4270
|
+
launchId,
|
|
4271
|
+
runtime: config.runtime,
|
|
4272
|
+
model: config.model,
|
|
4273
|
+
session_id_present: Boolean(config.sessionId),
|
|
4274
|
+
launch_id_present: Boolean(launchId),
|
|
4275
|
+
wake_message_present: Boolean(wakeMessage),
|
|
4276
|
+
unread_channels_count: unreadSummary ? Object.keys(unreadSummary).length : 0,
|
|
4277
|
+
resume_prompt_present: Boolean(resumePrompt),
|
|
4278
|
+
queue_depth: this.agentStartQueue.length,
|
|
4279
|
+
active_starts: this.activeAgentStartCount,
|
|
4280
|
+
max_concurrent_starts: this.maxConcurrentAgentStarts,
|
|
4281
|
+
min_start_interval_ms: this.agentStartIntervalMs
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
getDeliveryTraceContext(message) {
|
|
4285
|
+
return this.deliveryTraceContexts.get(message) ?? {};
|
|
4286
|
+
}
|
|
4287
|
+
deliveryTraceAttrs(agentId, message, attrs = {}) {
|
|
4288
|
+
const context = this.getDeliveryTraceContext(message);
|
|
4289
|
+
const deliveryCorrelationId = context.deliveryId ?? message.message_id;
|
|
4290
|
+
return {
|
|
4291
|
+
agentId,
|
|
4292
|
+
deliveryId: context.deliveryId,
|
|
4293
|
+
delivery_correlation_id: deliveryCorrelationId,
|
|
4294
|
+
channel_type: message.channel_type,
|
|
4295
|
+
sender_type: message.sender_type,
|
|
4296
|
+
messageId: message.message_id,
|
|
4297
|
+
message_id_present: Boolean(message.message_id),
|
|
4298
|
+
...attrs
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4025
4301
|
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4302
|
+
this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4026
4303
|
if (this.agents.has(agentId)) {
|
|
4304
|
+
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
4305
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4306
|
+
reason: "already_running"
|
|
4307
|
+
});
|
|
4027
4308
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
4028
4309
|
return;
|
|
4029
4310
|
}
|
|
4030
4311
|
if (this.agentsStarting.has(agentId)) {
|
|
4312
|
+
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
4313
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4314
|
+
reason: "already_starting"
|
|
4315
|
+
});
|
|
4031
4316
|
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
4032
4317
|
return;
|
|
4033
4318
|
}
|
|
4034
4319
|
if (this.queuedAgentStarts.has(agentId)) {
|
|
4320
|
+
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
4321
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4322
|
+
reason: "already_queued"
|
|
4323
|
+
});
|
|
4035
4324
|
logger.info(`[Agent ${agentId}] Start ignored (startup already queued)`);
|
|
4036
4325
|
return;
|
|
4037
4326
|
}
|
|
@@ -4048,6 +4337,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4048
4337
|
};
|
|
4049
4338
|
this.agentStartQueue.push(item);
|
|
4050
4339
|
this.queuedAgentStarts.set(agentId, item);
|
|
4340
|
+
this.recordDaemonTrace("daemon.agent.start.queued", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4051
4341
|
logger.info(
|
|
4052
4342
|
`[Agent ${agentId}] Start queued (queue=${this.agentStartQueue.length}, active=${this.activeAgentStartCount}, max=${this.maxConcurrentAgentStarts}, interval=${this.agentStartIntervalMs}ms)`
|
|
4053
4343
|
);
|
|
@@ -4063,6 +4353,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4063
4353
|
const elapsed = Date.now() - this.lastAgentStartAt;
|
|
4064
4354
|
const waitMs = shouldRateLimit ? Math.max(0, this.agentStartIntervalMs - elapsed) : 0;
|
|
4065
4355
|
if (waitMs > 0) {
|
|
4356
|
+
this.recordDaemonTrace("daemon.agent.start.rate_limited", {
|
|
4357
|
+
...this.startQueueTraceAttrs(next.agentId, next.config, next.wakeMessage, next.unreadSummary, next.resumePrompt, next.launchId),
|
|
4358
|
+
wait_ms: waitMs
|
|
4359
|
+
});
|
|
4066
4360
|
this.agentStartPumpTimer = setTimeout(() => {
|
|
4067
4361
|
this.agentStartPumpTimer = null;
|
|
4068
4362
|
this.pumpAgentStartQueue();
|
|
@@ -4072,23 +4366,31 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4072
4366
|
const item = this.agentStartQueue.shift();
|
|
4073
4367
|
if (!item) return;
|
|
4074
4368
|
if (this.queuedAgentStarts.get(item.agentId) !== item) {
|
|
4369
|
+
this.recordDaemonTrace("daemon.agent.start.skipped", {
|
|
4370
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4371
|
+
reason: "stale_queue_item"
|
|
4372
|
+
});
|
|
4075
4373
|
this.pumpAgentStartQueue();
|
|
4076
4374
|
return;
|
|
4077
4375
|
}
|
|
4078
4376
|
this.queuedAgentStarts.delete(item.agentId);
|
|
4079
4377
|
if (this.agents.has(item.agentId) || this.agentsStarting.has(item.agentId)) {
|
|
4378
|
+
this.recordDaemonTrace("daemon.agent.start.skipped", {
|
|
4379
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4380
|
+
reason: "already_running_or_starting"
|
|
4381
|
+
});
|
|
4080
4382
|
logger.info(`[Agent ${item.agentId}] Queued start skipped (already running or starting)`);
|
|
4081
4383
|
item.resolve();
|
|
4082
4384
|
this.pumpAgentStartQueue();
|
|
4083
4385
|
return;
|
|
4084
4386
|
}
|
|
4085
4387
|
this.activeAgentStartCount++;
|
|
4086
|
-
this.activeAgentStartPermits.add(item.agentId);
|
|
4087
4388
|
this.lastAgentStartAt = Date.now();
|
|
4088
4389
|
this.lastAgentStartAgentId = item.agentId;
|
|
4089
4390
|
logger.info(
|
|
4090
4391
|
`[Agent ${item.agentId}] Dequeued start (remaining=${this.agentStartQueue.length}, active=${this.activeAgentStartCount})`
|
|
4091
4392
|
);
|
|
4393
|
+
this.recordDaemonTrace("daemon.agent.start.dequeued", this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId));
|
|
4092
4394
|
this.startAgentNow(
|
|
4093
4395
|
item.agentId,
|
|
4094
4396
|
item.config,
|
|
@@ -4096,19 +4398,28 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4096
4398
|
item.unreadSummary,
|
|
4097
4399
|
item.resumePrompt,
|
|
4098
4400
|
item.launchId
|
|
4099
|
-
).then(
|
|
4100
|
-
this.
|
|
4401
|
+
).then(() => {
|
|
4402
|
+
this.releaseAgentStartSlot(item.agentId, "spawn attempted");
|
|
4403
|
+
item.resolve();
|
|
4404
|
+
}, (err) => {
|
|
4405
|
+
this.releaseAgentStartSlot(item.agentId, "start failed");
|
|
4101
4406
|
item.reject(err);
|
|
4102
4407
|
});
|
|
4103
4408
|
}
|
|
4104
|
-
|
|
4105
|
-
if (
|
|
4409
|
+
releaseAgentStartSlot(agentId, reason) {
|
|
4410
|
+
if (this.activeAgentStartCount <= 0) return;
|
|
4106
4411
|
this.activeAgentStartCount = Math.max(0, this.activeAgentStartCount - 1);
|
|
4412
|
+
this.recordDaemonTrace("daemon.agent.start.slot_released", {
|
|
4413
|
+
agentId,
|
|
4414
|
+
reason,
|
|
4415
|
+
active_starts: this.activeAgentStartCount,
|
|
4416
|
+
queue_depth: this.agentStartQueue.length,
|
|
4417
|
+
max_concurrent_starts: this.maxConcurrentAgentStarts
|
|
4418
|
+
});
|
|
4107
4419
|
logger.info(
|
|
4108
|
-
`[Agent ${agentId}] Start
|
|
4420
|
+
`[Agent ${agentId}] Start slot released (${reason}) (active=${this.activeAgentStartCount}, queue=${this.agentStartQueue.length})`
|
|
4109
4421
|
);
|
|
4110
4422
|
this.pumpAgentStartQueue();
|
|
4111
|
-
return true;
|
|
4112
4423
|
}
|
|
4113
4424
|
cancelQueuedAgentStart(agentId, reason) {
|
|
4114
4425
|
const item = this.queuedAgentStarts.get(agentId);
|
|
@@ -4120,6 +4431,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4120
4431
|
clearTimeout(this.agentStartPumpTimer);
|
|
4121
4432
|
this.agentStartPumpTimer = null;
|
|
4122
4433
|
}
|
|
4434
|
+
this.recordDaemonTrace("daemon.agent.start.cancelled", {
|
|
4435
|
+
...this.startQueueTraceAttrs(agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4436
|
+
reason
|
|
4437
|
+
}, "cancelled");
|
|
4123
4438
|
logger.info(`[Agent ${agentId}] Queued start cancelled (${reason})`);
|
|
4124
4439
|
item.resolve();
|
|
4125
4440
|
return true;
|
|
@@ -4127,6 +4442,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4127
4442
|
cancelAllQueuedAgentStarts(reason) {
|
|
4128
4443
|
for (const item of this.agentStartQueue) {
|
|
4129
4444
|
if (this.queuedAgentStarts.get(item.agentId) === item) {
|
|
4445
|
+
this.recordDaemonTrace("daemon.agent.start.cancelled", {
|
|
4446
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
4447
|
+
reason
|
|
4448
|
+
}, "cancelled");
|
|
4130
4449
|
logger.info(`[Agent ${item.agentId}] Queued start cancelled (${reason})`);
|
|
4131
4450
|
item.resolve();
|
|
4132
4451
|
}
|
|
@@ -4141,14 +4460,23 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4141
4460
|
}
|
|
4142
4461
|
async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4143
4462
|
if (this.agents.has(agentId)) {
|
|
4463
|
+
this.recordDaemonTrace("daemon.agent.spawn.skipped", {
|
|
4464
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4465
|
+
reason: "already_running"
|
|
4466
|
+
});
|
|
4144
4467
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
4145
4468
|
return;
|
|
4146
4469
|
}
|
|
4147
4470
|
if (this.agentsStarting.has(agentId)) {
|
|
4471
|
+
this.recordDaemonTrace("daemon.agent.spawn.skipped", {
|
|
4472
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4473
|
+
reason: "already_starting"
|
|
4474
|
+
});
|
|
4148
4475
|
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
4149
4476
|
return;
|
|
4150
4477
|
}
|
|
4151
4478
|
this.agentsStarting.add(agentId);
|
|
4479
|
+
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4152
4480
|
try {
|
|
4153
4481
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
4154
4482
|
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
@@ -4208,6 +4536,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up, or respond to t
|
|
|
4208
4536
|
prompt += `
|
|
4209
4537
|
|
|
4210
4538
|
Respond as appropriate \u2014 ${dynamicReplyInstruction(driver)}, or take action as needed. Complete ALL your work before stopping.
|
|
4539
|
+
${RESPONSE_TARGET_HINT}
|
|
4211
4540
|
|
|
4212
4541
|
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
4542
|
prompt += getBusyDeliveryNote(driver);
|
|
@@ -4240,8 +4569,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4240
4569
|
});
|
|
4241
4570
|
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
4242
4571
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
4572
|
+
this.recordDaemonTrace("daemon.agent.spawn.deferred", {
|
|
4573
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4574
|
+
pending_messages_count: pendingMessages.length,
|
|
4575
|
+
reason: "defer_until_concrete_message"
|
|
4576
|
+
});
|
|
4243
4577
|
logger.info(`[Agent ${agentId}] Deferred ${driver.id} spawn until first concrete message`);
|
|
4244
|
-
this.releaseAgentStartPermit(agentId, "spawn deferred");
|
|
4245
4578
|
for (const message of pendingMessages) {
|
|
4246
4579
|
this.deliverMessage(agentId, message);
|
|
4247
4580
|
}
|
|
@@ -4258,6 +4591,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4258
4591
|
daemonApiKey: this.daemonApiKey,
|
|
4259
4592
|
launchId: launchId || null
|
|
4260
4593
|
});
|
|
4594
|
+
this.recordDaemonTrace("daemon.agent.spawn.created", {
|
|
4595
|
+
...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
4596
|
+
detached: false,
|
|
4597
|
+
new_session: false,
|
|
4598
|
+
process_pid_present: typeof proc.pid === "number"
|
|
4599
|
+
});
|
|
4261
4600
|
const agentProcess = {
|
|
4262
4601
|
process: proc,
|
|
4263
4602
|
driver,
|
|
@@ -4292,7 +4631,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4292
4631
|
};
|
|
4293
4632
|
this.startingInboxes.delete(agentId);
|
|
4294
4633
|
this.agents.set(agentId, agentProcess);
|
|
4295
|
-
this.
|
|
4634
|
+
this.idleAgentConfigs.set(agentId, {
|
|
4635
|
+
config: { ...effectiveConfig, sessionId: effectiveConfig.sessionId || null },
|
|
4636
|
+
sessionId: effectiveConfig.sessionId || null,
|
|
4637
|
+
launchId: launchId || null
|
|
4638
|
+
});
|
|
4639
|
+
this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0);
|
|
4296
4640
|
this.agentsStarting.delete(agentId);
|
|
4297
4641
|
if (config.runtimeProfileControl) {
|
|
4298
4642
|
this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
|
|
@@ -4331,6 +4675,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4331
4675
|
proc.on("error", (err) => {
|
|
4332
4676
|
const current = this.agents.get(agentId);
|
|
4333
4677
|
if (current) current.spawnError = err.message;
|
|
4678
|
+
this.recordDaemonTrace("daemon.agent.process.error", {
|
|
4679
|
+
agentId,
|
|
4680
|
+
launchId: current?.launchId || void 0,
|
|
4681
|
+
runtime: config.runtime,
|
|
4682
|
+
model: config.model,
|
|
4683
|
+
error_class: err.name || typeof err
|
|
4684
|
+
}, "error");
|
|
4334
4685
|
logger.error(`[Agent ${agentId}] Process error: ${err.message}`);
|
|
4335
4686
|
});
|
|
4336
4687
|
proc.on("exit", (code, signal) => {
|
|
@@ -4339,13 +4690,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4339
4690
|
current.exitCode = code;
|
|
4340
4691
|
current.exitSignal = signal;
|
|
4341
4692
|
}
|
|
4693
|
+
this.recordDaemonTrace("daemon.agent.process.exited", {
|
|
4694
|
+
agentId,
|
|
4695
|
+
launchId: current?.launchId || void 0,
|
|
4696
|
+
runtime: config.runtime,
|
|
4697
|
+
model: config.model,
|
|
4698
|
+
exit_code: code,
|
|
4699
|
+
exit_signal: signal,
|
|
4700
|
+
clean_exit: code === 0,
|
|
4701
|
+
runtime_trace_active: Boolean(current?.runtimeTraceSpan),
|
|
4702
|
+
inbox_count: current?.inbox.length ?? 0,
|
|
4703
|
+
pending_notification_count: current?.pendingNotificationCount ?? 0
|
|
4704
|
+
});
|
|
4342
4705
|
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
4343
4706
|
});
|
|
4344
4707
|
proc.on("close", (code, signal) => {
|
|
4345
4708
|
if (this.agents.has(agentId)) {
|
|
4346
4709
|
const ap = this.agents.get(agentId);
|
|
4347
4710
|
if (ap.process !== proc) return;
|
|
4348
|
-
this.releaseAgentStartPermit(agentId, "process closed");
|
|
4349
4711
|
if (ap.notificationTimer) {
|
|
4350
4712
|
clearTimeout(ap.notificationTimer);
|
|
4351
4713
|
}
|
|
@@ -4403,7 +4765,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4403
4765
|
}
|
|
4404
4766
|
if (processEndedCleanly) {
|
|
4405
4767
|
let queuedWakeMessage;
|
|
4406
|
-
if (!ap.driver.supportsStdinNotification) {
|
|
4768
|
+
if (!ap.driver.supportsStdinNotification || ap.expectedTerminationReason === "stalled_recovery") {
|
|
4407
4769
|
while (ap.inbox.length > 0) {
|
|
4408
4770
|
const candidate = ap.inbox.shift();
|
|
4409
4771
|
if (this.shouldDeferWakeMessage(agentId, ap.driver, candidate)) continue;
|
|
@@ -4509,6 +4871,66 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4509
4871
|
}
|
|
4510
4872
|
return leftKeys.every((key) => left?.[key] === right?.[key]);
|
|
4511
4873
|
}
|
|
4874
|
+
enqueueRuntimeProfileNotification(agentId, ap, message, kind, key) {
|
|
4875
|
+
ap.inbox.push(message);
|
|
4876
|
+
if (ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
4877
|
+
ap.pendingNotificationCount++;
|
|
4878
|
+
if (ap.driver.busyDeliveryMode === "gated") {
|
|
4879
|
+
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
4880
|
+
reason: "runtime_profile",
|
|
4881
|
+
kind,
|
|
4882
|
+
pendingMessages: ap.inbox.length
|
|
4883
|
+
});
|
|
4884
|
+
} else if (!ap.notificationTimer) {
|
|
4885
|
+
ap.notificationTimer = setTimeout(() => {
|
|
4886
|
+
this.sendStdinNotification(agentId);
|
|
4887
|
+
}, 3e3);
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
this.recordDaemonTrace("daemon.agent.runtime_profile.routed", {
|
|
4891
|
+
agentId,
|
|
4892
|
+
kind,
|
|
4893
|
+
key_present: Boolean(key),
|
|
4894
|
+
outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
|
|
4895
|
+
runtime: ap.config.runtime,
|
|
4896
|
+
session_id_present: Boolean(ap.sessionId),
|
|
4897
|
+
launchId: ap.launchId || void 0,
|
|
4898
|
+
inbox_count: ap.inbox.length,
|
|
4899
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
4900
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
4901
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification
|
|
4902
|
+
});
|
|
4903
|
+
logger.info(
|
|
4904
|
+
`[Agent ${agentId}] Queued runtime profile ${kind} ${key} for ${ap.sessionId ? "busy" : "pre-session"} ${ap.driver.id} delivery`
|
|
4905
|
+
);
|
|
4906
|
+
}
|
|
4907
|
+
queueRuntimeProfileNotificationDuringStart(agentId, message, kind, key) {
|
|
4908
|
+
const pending = this.startingInboxes.get(agentId) || [];
|
|
4909
|
+
pending.push(message);
|
|
4910
|
+
this.startingInboxes.set(agentId, pending);
|
|
4911
|
+
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
4912
|
+
this.recordDaemonTrace("daemon.agent.runtime_profile.routed", {
|
|
4913
|
+
agentId,
|
|
4914
|
+
kind,
|
|
4915
|
+
key_present: Boolean(key),
|
|
4916
|
+
outcome: "queued_during_start",
|
|
4917
|
+
startup_pending: true,
|
|
4918
|
+
starting_inbox_count: pending.length,
|
|
4919
|
+
launchId: queuedStart?.launchId
|
|
4920
|
+
});
|
|
4921
|
+
logger.info(`[Agent ${agentId}] Queued runtime profile ${kind} ${key} during startup`);
|
|
4922
|
+
}
|
|
4923
|
+
splitRuntimeProfileControlBatch(messages) {
|
|
4924
|
+
const controlMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message));
|
|
4925
|
+
if (controlMessages.length === 0 || controlMessages.length === messages.length) {
|
|
4926
|
+
return { nextMessages: messages, deferredMessages: [] };
|
|
4927
|
+
}
|
|
4928
|
+
const deferredMessages = messages.filter((message) => !runtimeProfileNotificationFromMessage(message));
|
|
4929
|
+
return { nextMessages: controlMessages, deferredMessages };
|
|
4930
|
+
}
|
|
4931
|
+
containsOrdinaryInboxMessage(messages) {
|
|
4932
|
+
return messages.some((message) => !runtimeProfileNotificationFromMessage(message));
|
|
4933
|
+
}
|
|
4512
4934
|
async stopAgent(agentId, { wait = false, silent = false } = {}) {
|
|
4513
4935
|
this.cancelQueuedAgentStart(agentId, "stop requested");
|
|
4514
4936
|
this.idleAgentConfigs.delete(agentId);
|
|
@@ -4519,7 +4941,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4519
4941
|
}
|
|
4520
4942
|
return;
|
|
4521
4943
|
}
|
|
4522
|
-
this.releaseAgentStartPermit(agentId, "stop requested");
|
|
4523
4944
|
if (ap.notificationTimer) {
|
|
4524
4945
|
clearTimeout(ap.notificationTimer);
|
|
4525
4946
|
}
|
|
@@ -4558,51 +4979,155 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4558
4979
|
});
|
|
4559
4980
|
}
|
|
4560
4981
|
}
|
|
4561
|
-
deliverMessage(agentId, message) {
|
|
4982
|
+
deliverMessage(agentId, message, traceContext = {}) {
|
|
4983
|
+
if (traceContext.deliveryId) {
|
|
4984
|
+
this.deliveryTraceContexts.set(message, traceContext);
|
|
4985
|
+
}
|
|
4562
4986
|
const ap = this.agents.get(agentId);
|
|
4563
4987
|
if (!ap) {
|
|
4564
4988
|
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
4989
|
+
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
4565
4990
|
const pending = this.startingInboxes.get(agentId) || [];
|
|
4566
4991
|
pending.push(message);
|
|
4567
4992
|
this.startingInboxes.set(agentId, pending);
|
|
4568
|
-
|
|
4993
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
4994
|
+
outcome: "queued_during_start",
|
|
4995
|
+
accepted: true,
|
|
4996
|
+
process_present: false,
|
|
4997
|
+
startup_pending: true,
|
|
4998
|
+
starting_inbox_count: pending.length,
|
|
4999
|
+
launchId: queuedStart?.launchId
|
|
5000
|
+
}));
|
|
5001
|
+
return true;
|
|
4569
5002
|
}
|
|
4570
5003
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
4571
5004
|
if (cached) {
|
|
4572
5005
|
const driver = this.driverResolver(cached.config.runtime || "claude");
|
|
4573
5006
|
if (this.shouldDeferWakeMessage(agentId, driver, message)) {
|
|
4574
|
-
|
|
5007
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5008
|
+
outcome: "deferred_wake_message",
|
|
5009
|
+
accepted: true,
|
|
5010
|
+
process_present: false,
|
|
5011
|
+
cached_idle_config_present: true,
|
|
5012
|
+
runtime: cached.config.runtime,
|
|
5013
|
+
session_id_present: Boolean(cached.sessionId),
|
|
5014
|
+
launchId: cached.launchId || void 0
|
|
5015
|
+
}));
|
|
5016
|
+
return true;
|
|
4575
5017
|
}
|
|
4576
5018
|
logger.info(`[Agent ${agentId}] Starting from idle state for new message`);
|
|
4577
5019
|
this.idleAgentConfigs.delete(agentId);
|
|
5020
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5021
|
+
outcome: "auto_restart_from_idle",
|
|
5022
|
+
accepted: true,
|
|
5023
|
+
process_present: false,
|
|
5024
|
+
cached_idle_config_present: true,
|
|
5025
|
+
runtime: cached.config.runtime,
|
|
5026
|
+
session_id_present: Boolean(cached.sessionId),
|
|
5027
|
+
launchId: cached.launchId || void 0
|
|
5028
|
+
}));
|
|
4578
5029
|
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
|
|
4579
5030
|
logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
|
|
4580
5031
|
});
|
|
5032
|
+
return true;
|
|
4581
5033
|
}
|
|
4582
|
-
|
|
5034
|
+
logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
|
|
5035
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5036
|
+
outcome: "rejected_no_process",
|
|
5037
|
+
accepted: false,
|
|
5038
|
+
process_present: false,
|
|
5039
|
+
cached_idle_config_present: false
|
|
5040
|
+
}), "error");
|
|
5041
|
+
this.sendAgentStatus(agentId, "inactive", null);
|
|
5042
|
+
this.broadcastActivity(agentId, "offline", "Process unavailable; restart required");
|
|
5043
|
+
return false;
|
|
4583
5044
|
}
|
|
4584
5045
|
if (this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
|
|
4585
|
-
|
|
5046
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5047
|
+
outcome: "deferred_wake_message",
|
|
5048
|
+
accepted: true,
|
|
5049
|
+
process_present: true,
|
|
5050
|
+
runtime: ap.config.runtime,
|
|
5051
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5052
|
+
launchId: ap.launchId || void 0,
|
|
5053
|
+
is_idle: ap.isIdle,
|
|
5054
|
+
inbox_count: ap.inbox.length
|
|
5055
|
+
}));
|
|
5056
|
+
return true;
|
|
4586
5057
|
}
|
|
4587
5058
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
4588
5059
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
4589
5060
|
nextMessages.push(message);
|
|
4590
5061
|
ap.isIdle = false;
|
|
4591
|
-
this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery");
|
|
5062
|
+
this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", nextMessages);
|
|
4592
5063
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
4593
|
-
this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
|
|
4594
|
-
|
|
5064
|
+
const stdinAccepted = this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
|
|
5065
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5066
|
+
outcome: "stdin_idle_delivery",
|
|
5067
|
+
accepted: true,
|
|
5068
|
+
process_present: true,
|
|
5069
|
+
runtime: ap.config.runtime,
|
|
5070
|
+
session_id_present: true,
|
|
5071
|
+
launchId: ap.launchId || void 0,
|
|
5072
|
+
stdin_delivery_accepted: stdinAccepted,
|
|
5073
|
+
delivered_messages_count: nextMessages.length
|
|
5074
|
+
}));
|
|
5075
|
+
return true;
|
|
4595
5076
|
}
|
|
4596
5077
|
ap.inbox.push(message);
|
|
4597
|
-
if (
|
|
4598
|
-
|
|
5078
|
+
if (this.recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap)) {
|
|
5079
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5080
|
+
outcome: "queued_stalled_recovery",
|
|
5081
|
+
accepted: true,
|
|
5082
|
+
process_present: true,
|
|
5083
|
+
runtime: ap.config.runtime,
|
|
5084
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5085
|
+
launchId: ap.launchId || void 0,
|
|
5086
|
+
inbox_count: ap.inbox.length
|
|
5087
|
+
}));
|
|
5088
|
+
return true;
|
|
5089
|
+
}
|
|
5090
|
+
if (!ap.driver.supportsStdinNotification) {
|
|
5091
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5092
|
+
outcome: "queued_busy_non_stdin",
|
|
5093
|
+
accepted: true,
|
|
5094
|
+
process_present: true,
|
|
5095
|
+
runtime: ap.config.runtime,
|
|
5096
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5097
|
+
launchId: ap.launchId || void 0,
|
|
5098
|
+
inbox_count: ap.inbox.length
|
|
5099
|
+
}));
|
|
5100
|
+
return true;
|
|
5101
|
+
}
|
|
5102
|
+
if (!ap.sessionId) {
|
|
5103
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5104
|
+
outcome: "queued_before_session",
|
|
5105
|
+
accepted: true,
|
|
5106
|
+
process_present: true,
|
|
5107
|
+
runtime: ap.config.runtime,
|
|
5108
|
+
session_id_present: false,
|
|
5109
|
+
launchId: ap.launchId || void 0,
|
|
5110
|
+
inbox_count: ap.inbox.length
|
|
5111
|
+
}));
|
|
5112
|
+
return true;
|
|
5113
|
+
}
|
|
4599
5114
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
4600
5115
|
ap.pendingNotificationCount++;
|
|
4601
5116
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
4602
5117
|
reason: "busy_message",
|
|
4603
5118
|
pendingMessages: ap.inbox.length
|
|
4604
5119
|
});
|
|
4605
|
-
|
|
5120
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5121
|
+
outcome: "queued_busy_gated",
|
|
5122
|
+
accepted: true,
|
|
5123
|
+
process_present: true,
|
|
5124
|
+
runtime: ap.config.runtime,
|
|
5125
|
+
session_id_present: true,
|
|
5126
|
+
launchId: ap.launchId || void 0,
|
|
5127
|
+
inbox_count: ap.inbox.length,
|
|
5128
|
+
pending_notification_count: ap.pendingNotificationCount
|
|
5129
|
+
}));
|
|
5130
|
+
return true;
|
|
4606
5131
|
}
|
|
4607
5132
|
ap.pendingNotificationCount++;
|
|
4608
5133
|
if (!ap.notificationTimer) {
|
|
@@ -4610,6 +5135,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4610
5135
|
this.sendStdinNotification(agentId);
|
|
4611
5136
|
}, 3e3);
|
|
4612
5137
|
}
|
|
5138
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
5139
|
+
outcome: "queued_busy_notification",
|
|
5140
|
+
accepted: true,
|
|
5141
|
+
process_present: true,
|
|
5142
|
+
runtime: ap.config.runtime,
|
|
5143
|
+
session_id_present: true,
|
|
5144
|
+
inbox_count: ap.inbox.length,
|
|
5145
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
5146
|
+
notification_timer_present: Boolean(ap.notificationTimer)
|
|
5147
|
+
}));
|
|
5148
|
+
return true;
|
|
4613
5149
|
}
|
|
4614
5150
|
async resetWorkspace(agentId) {
|
|
4615
5151
|
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
@@ -4643,6 +5179,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4643
5179
|
getIdleAgentSessionIds() {
|
|
4644
5180
|
const result = [];
|
|
4645
5181
|
for (const [agentId, { sessionId, launchId }] of this.idleAgentConfigs) {
|
|
5182
|
+
if (this.agents.has(agentId)) continue;
|
|
4646
5183
|
if (sessionId) result.push({ agentId, sessionId, launchId });
|
|
4647
5184
|
}
|
|
4648
5185
|
return result;
|
|
@@ -4694,7 +5231,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4694
5231
|
}
|
|
4695
5232
|
return reports;
|
|
4696
5233
|
}
|
|
4697
|
-
deliverRuntimeProfileNotification(agentId, key, kind, content) {
|
|
5234
|
+
deliverRuntimeProfileNotification(agentId, key, kind, content, traceparent) {
|
|
5235
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.inject", {
|
|
5236
|
+
parent: parseTraceparent(traceparent),
|
|
5237
|
+
surface: "daemon",
|
|
5238
|
+
kind: "consumer",
|
|
5239
|
+
attrs: {
|
|
5240
|
+
agentId,
|
|
5241
|
+
control_kind: kind,
|
|
5242
|
+
key_present: Boolean(key)
|
|
5243
|
+
}
|
|
5244
|
+
});
|
|
4698
5245
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4699
5246
|
const message = {
|
|
4700
5247
|
channel_id: "system",
|
|
@@ -4705,18 +5252,65 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4705
5252
|
sender_type: "system",
|
|
4706
5253
|
content,
|
|
4707
5254
|
timestamp: now,
|
|
4708
|
-
message_id: `${kind === "migration" ? RUNTIME_PROFILE_MIGRATION_MESSAGE_PREFIX : RUNTIME_PROFILE_DAEMON_NOTICE_MESSAGE_PREFIX}${key}
|
|
5255
|
+
message_id: `${kind === "migration" ? RUNTIME_PROFILE_MIGRATION_MESSAGE_PREFIX : RUNTIME_PROFILE_DAEMON_NOTICE_MESSAGE_PREFIX}${key}`,
|
|
5256
|
+
traceparent: formatTraceparent(span.context)
|
|
4709
5257
|
};
|
|
4710
5258
|
const ap = this.agents.get(agentId);
|
|
5259
|
+
if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.driver.busyDeliveryMode === "direct")) {
|
|
5260
|
+
this.enqueueRuntimeProfileNotification(agentId, ap, message, kind, key);
|
|
5261
|
+
span.end("ok", {
|
|
5262
|
+
attrs: {
|
|
5263
|
+
outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
|
|
5264
|
+
runtime: ap.config.runtime,
|
|
5265
|
+
launchId: ap.launchId || void 0,
|
|
5266
|
+
session_id_present: Boolean(ap.sessionId),
|
|
5267
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
5268
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
return true;
|
|
5272
|
+
}
|
|
4711
5273
|
if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
|
|
4712
5274
|
ap.isIdle = false;
|
|
4713
5275
|
this.startRuntimeTrace(agentId, ap, "runtime-profile");
|
|
4714
|
-
this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
4715
|
-
|
|
5276
|
+
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
5277
|
+
span.end(written ? "ok" : "error", {
|
|
5278
|
+
attrs: {
|
|
5279
|
+
outcome: written ? "stdin_idle" : "stdin_failed",
|
|
5280
|
+
runtime: ap.config.runtime,
|
|
5281
|
+
launchId: ap.launchId || void 0,
|
|
5282
|
+
session_id_present: true,
|
|
5283
|
+
supports_stdin_notification: true,
|
|
5284
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode
|
|
5285
|
+
}
|
|
5286
|
+
});
|
|
5287
|
+
return written;
|
|
4716
5288
|
}
|
|
4717
5289
|
if (ap?.sessionId && ap.driver.busyDeliveryMode === "direct") {
|
|
4718
|
-
this.deliverMessagesViaStdin(agentId, ap, [message], "busy");
|
|
4719
|
-
|
|
5290
|
+
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "busy");
|
|
5291
|
+
span.end(written ? "ok" : "error", {
|
|
5292
|
+
attrs: {
|
|
5293
|
+
outcome: written ? "stdin_busy" : "stdin_failed",
|
|
5294
|
+
runtime: ap.config.runtime,
|
|
5295
|
+
launchId: ap.launchId || void 0,
|
|
5296
|
+
session_id_present: true,
|
|
5297
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
5298
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode
|
|
5299
|
+
}
|
|
5300
|
+
});
|
|
5301
|
+
return written;
|
|
5302
|
+
}
|
|
5303
|
+
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
5304
|
+
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
5305
|
+
this.queueRuntimeProfileNotificationDuringStart(agentId, message, kind, key);
|
|
5306
|
+
span.end("ok", {
|
|
5307
|
+
attrs: {
|
|
5308
|
+
outcome: "queued_during_start",
|
|
5309
|
+
startup_pending: true,
|
|
5310
|
+
launchId: queuedStart?.launchId
|
|
5311
|
+
}
|
|
5312
|
+
});
|
|
5313
|
+
return true;
|
|
4720
5314
|
}
|
|
4721
5315
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
4722
5316
|
if (cached) {
|
|
@@ -4726,9 +5320,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4726
5320
|
logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
|
|
4727
5321
|
this.idleAgentConfigs.set(agentId, cached);
|
|
4728
5322
|
});
|
|
4729
|
-
|
|
5323
|
+
span.end("ok", {
|
|
5324
|
+
attrs: {
|
|
5325
|
+
outcome: "restart_queued",
|
|
5326
|
+
runtime: cached.config.runtime,
|
|
5327
|
+
launchId: cached.launchId || void 0,
|
|
5328
|
+
session_id_present: Boolean(cached.sessionId)
|
|
5329
|
+
}
|
|
5330
|
+
});
|
|
5331
|
+
return true;
|
|
4730
5332
|
}
|
|
4731
5333
|
logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
|
|
5334
|
+
span.end("ok", { attrs: { outcome: "no_path" } });
|
|
5335
|
+
return false;
|
|
4732
5336
|
}
|
|
4733
5337
|
ackInjectedRuntimeProfileMessages(agentId, messages, launchId) {
|
|
4734
5338
|
for (const message of messages) {
|
|
@@ -4741,19 +5345,32 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4741
5345
|
type: "agent:runtime_profile:migration:ack",
|
|
4742
5346
|
agentId,
|
|
4743
5347
|
migrationKey: notification.key,
|
|
4744
|
-
launchId: launchId || void 0
|
|
5348
|
+
launchId: launchId || void 0,
|
|
5349
|
+
traceparent: message.traceparent
|
|
4745
5350
|
});
|
|
4746
5351
|
} else {
|
|
4747
5352
|
this.sendToServer({
|
|
4748
5353
|
type: "agent:runtime_profile:daemon_release_notice:ack",
|
|
4749
5354
|
agentId,
|
|
4750
5355
|
noticeKey: notification.key,
|
|
4751
|
-
launchId: launchId || void 0
|
|
5356
|
+
launchId: launchId || void 0,
|
|
5357
|
+
traceparent: message.traceparent
|
|
4752
5358
|
});
|
|
4753
5359
|
}
|
|
4754
5360
|
}
|
|
4755
5361
|
}
|
|
4756
5362
|
ackInjectedRuntimeProfileControl(agentId, control, launchId) {
|
|
5363
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.inject", {
|
|
5364
|
+
surface: "daemon",
|
|
5365
|
+
kind: "internal",
|
|
5366
|
+
attrs: {
|
|
5367
|
+
agentId,
|
|
5368
|
+
control_kind: control.kind,
|
|
5369
|
+
key_present: Boolean(control.key),
|
|
5370
|
+
launchId: launchId || void 0,
|
|
5371
|
+
source: "agent_config"
|
|
5372
|
+
}
|
|
5373
|
+
});
|
|
4757
5374
|
const title = runtimeProfileNotificationTitle(control.kind);
|
|
4758
5375
|
this.broadcastActivity(agentId, "working", title, [{ kind: "system", title, text: control.message }], launchId);
|
|
4759
5376
|
if (control.kind === "migration") {
|
|
@@ -4761,24 +5378,41 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4761
5378
|
type: "agent:runtime_profile:migration:ack",
|
|
4762
5379
|
agentId,
|
|
4763
5380
|
migrationKey: control.key,
|
|
4764
|
-
launchId: launchId || void 0
|
|
5381
|
+
launchId: launchId || void 0,
|
|
5382
|
+
traceparent: formatTraceparent(span.context)
|
|
4765
5383
|
});
|
|
4766
5384
|
} else {
|
|
4767
5385
|
this.sendToServer({
|
|
4768
5386
|
type: "agent:runtime_profile:daemon_release_notice:ack",
|
|
4769
5387
|
agentId,
|
|
4770
5388
|
noticeKey: control.key,
|
|
4771
|
-
launchId: launchId || void 0
|
|
5389
|
+
launchId: launchId || void 0,
|
|
5390
|
+
traceparent: formatTraceparent(span.context)
|
|
4772
5391
|
});
|
|
4773
5392
|
}
|
|
5393
|
+
span.end("ok", { attrs: { outcome: "agent_config_ack_sent" } });
|
|
4774
5394
|
}
|
|
4775
5395
|
sendRuntimeProfileWireReport(report) {
|
|
5396
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
5397
|
+
surface: "daemon",
|
|
5398
|
+
kind: "producer",
|
|
5399
|
+
attrs: {
|
|
5400
|
+
agentId: report.agentId,
|
|
5401
|
+
launchId: report.launchId || void 0,
|
|
5402
|
+
runtime: report.facts.runtime,
|
|
5403
|
+
model_present: Boolean(report.facts.model),
|
|
5404
|
+
session_ref_present: Boolean(report.facts.sessionRef),
|
|
5405
|
+
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
5406
|
+
}
|
|
5407
|
+
});
|
|
4776
5408
|
this.sendToServer({
|
|
4777
5409
|
type: "agent:runtime_profile",
|
|
4778
5410
|
agentId: report.agentId,
|
|
4779
5411
|
facts: report.facts,
|
|
4780
|
-
launchId: report.launchId || void 0
|
|
5412
|
+
launchId: report.launchId || void 0,
|
|
5413
|
+
traceparent: formatTraceparent(span.context)
|
|
4781
5414
|
});
|
|
5415
|
+
span.end("ok");
|
|
4782
5416
|
}
|
|
4783
5417
|
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId) {
|
|
4784
5418
|
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId));
|
|
@@ -4821,32 +5455,21 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4821
5455
|
}
|
|
4822
5456
|
const info = await stat2(resolved);
|
|
4823
5457
|
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
5458
|
const ext = path11.extname(resolved).toLowerCase();
|
|
4844
|
-
if (
|
|
4845
|
-
|
|
5459
|
+
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
5460
|
+
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
5461
|
+
const content = await readFile(resolved, "utf-8");
|
|
5462
|
+
return { content, binary: false, size: info.size, encoding: "utf-8" };
|
|
5463
|
+
}
|
|
5464
|
+
const imageMimeType = WORKSPACE_IMAGE_MIME_BY_EXTENSION[ext];
|
|
5465
|
+
if (imageMimeType) {
|
|
5466
|
+
if (info.size > WORKSPACE_IMAGE_PREVIEW_MAX_BYTES) {
|
|
5467
|
+
return { content: null, binary: true, size: info.size, mimeType: imageMimeType };
|
|
5468
|
+
}
|
|
5469
|
+
const content = await readFile(resolved, "base64");
|
|
5470
|
+
return { content, binary: true, size: info.size, mimeType: imageMimeType, encoding: "base64" };
|
|
4846
5471
|
}
|
|
4847
|
-
|
|
4848
|
-
const content = await readFile(resolved, "utf-8");
|
|
4849
|
-
return { content, binary: false };
|
|
5472
|
+
return { content: null, binary: true, size: info.size };
|
|
4850
5473
|
}
|
|
4851
5474
|
// Skill scanning
|
|
4852
5475
|
// Per-runtime skill search paths (relative to home dir for global, workspace dir for workspace).
|
|
@@ -4866,7 +5489,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4866
5489
|
async listSkills(agentId, runtimeHint) {
|
|
4867
5490
|
const agent = this.agents.get(agentId);
|
|
4868
5491
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
4869
|
-
const home =
|
|
5492
|
+
const home = os5.homedir();
|
|
4870
5493
|
const workspaceDir = path11.join(this.dataDir, agentId);
|
|
4871
5494
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
4872
5495
|
const globalResults = await Promise.all(
|
|
@@ -5096,7 +5719,30 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5096
5719
|
this.clearCompactionWatchdog(ap);
|
|
5097
5720
|
this.broadcastActivity(agentId, "working", detail, [{ kind: "compaction_finished" }]);
|
|
5098
5721
|
}
|
|
5099
|
-
|
|
5722
|
+
messagesTraceAttrs(messages) {
|
|
5723
|
+
if (!messages || messages.length === 0) return {};
|
|
5724
|
+
const first = messages[0];
|
|
5725
|
+
const context = this.getDeliveryTraceContext(first);
|
|
5726
|
+
const notification = runtimeProfileNotificationFromMessage(first);
|
|
5727
|
+
if (notification) {
|
|
5728
|
+
return {
|
|
5729
|
+
messages_count: messages.length,
|
|
5730
|
+
message_id_present: Boolean(first.message_id),
|
|
5731
|
+
deliveryId: context.deliveryId,
|
|
5732
|
+
delivery_correlation_id: context.deliveryId,
|
|
5733
|
+
control_kind: notification.kind,
|
|
5734
|
+
key_present: Boolean(notification.key)
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
return {
|
|
5738
|
+
messages_count: messages.length,
|
|
5739
|
+
messageId: first.message_id,
|
|
5740
|
+
message_id_present: Boolean(first.message_id),
|
|
5741
|
+
deliveryId: context.deliveryId,
|
|
5742
|
+
delivery_correlation_id: context.deliveryId ?? first.message_id
|
|
5743
|
+
};
|
|
5744
|
+
}
|
|
5745
|
+
startRuntimeTrace(agentId, ap, reason, messages) {
|
|
5100
5746
|
if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
|
|
5101
5747
|
const span = this.tracer.startSpan("daemon.runtime.turn", {
|
|
5102
5748
|
surface: "daemon",
|
|
@@ -5106,10 +5752,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5106
5752
|
runtime: ap.config.runtime,
|
|
5107
5753
|
model: ap.config.model,
|
|
5108
5754
|
reason,
|
|
5109
|
-
hasSession: Boolean(ap.sessionId)
|
|
5755
|
+
hasSession: Boolean(ap.sessionId),
|
|
5756
|
+
...this.messagesTraceAttrs(messages)
|
|
5110
5757
|
}
|
|
5111
5758
|
});
|
|
5112
|
-
span.addEvent("daemon.turn.started", { reason });
|
|
5759
|
+
span.addEvent("daemon.turn.started", { reason, ...this.messagesTraceAttrs(messages) });
|
|
5113
5760
|
ap.runtimeTraceSpan = span;
|
|
5114
5761
|
return span;
|
|
5115
5762
|
}
|
|
@@ -5221,6 +5868,48 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5221
5868
|
this.broadcastActivity(agentId, "error", `Runtime stalled: no runtime events for ${staleForMinutes}m`);
|
|
5222
5869
|
return true;
|
|
5223
5870
|
}
|
|
5871
|
+
recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap) {
|
|
5872
|
+
if (ap.inbox.length === 0) return false;
|
|
5873
|
+
if (ap.expectedTerminationReason === "stalled_recovery") {
|
|
5874
|
+
return true;
|
|
5875
|
+
}
|
|
5876
|
+
const directStdinRuntime = ap.driver.supportsStdinNotification && ap.driver.busyDeliveryMode === "direct";
|
|
5877
|
+
const canRestartDirectStdinProcess = directStdinRuntime && Boolean(ap.sessionId) && (ap.gatedSteering.outstandingToolUses === 0 || hasDirectStdinRecoveryEvidence(ap));
|
|
5878
|
+
const canRestartStalledProcess = !ap.driver.supportsStdinNotification || canRestartDirectStdinProcess;
|
|
5879
|
+
if (!canRestartStalledProcess) return false;
|
|
5880
|
+
const staleForMs = Date.now() - ap.lastRuntimeEventAt;
|
|
5881
|
+
if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgressStaleSince) return false;
|
|
5882
|
+
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
5883
|
+
ap.runtimeProgressStaleSince ??= Date.now();
|
|
5884
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
|
|
5885
|
+
ageMs: staleForMs,
|
|
5886
|
+
staleForMinutes,
|
|
5887
|
+
lastActivity: ap.lastActivity,
|
|
5888
|
+
pendingMessages: ap.inbox.length,
|
|
5889
|
+
recovery: "terminate_for_queued_message"
|
|
5890
|
+
});
|
|
5891
|
+
this.endRuntimeTrace(ap, "error", {
|
|
5892
|
+
outcome: "runtime-stalled",
|
|
5893
|
+
ageMs: staleForMs,
|
|
5894
|
+
lastActivity: ap.lastActivity,
|
|
5895
|
+
pendingMessages: ap.inbox.length,
|
|
5896
|
+
recovery: "terminate_for_queued_message"
|
|
5897
|
+
});
|
|
5898
|
+
ap.expectedTerminationReason = "stalled_recovery";
|
|
5899
|
+
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
|
|
5900
|
+
logger.warn(
|
|
5901
|
+
`[Agent ${agentId}] ${runtimeLabel} process stalled for ${staleForMinutes}m with ${ap.inbox.length} queued message(s); terminating for restart`
|
|
5902
|
+
);
|
|
5903
|
+
this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
|
|
5904
|
+
try {
|
|
5905
|
+
ap.process.kill("SIGTERM");
|
|
5906
|
+
} catch (err) {
|
|
5907
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5908
|
+
logger.warn(`[Agent ${agentId}] Failed to terminate stalled ${runtimeLabel} process: ${reason}`);
|
|
5909
|
+
return false;
|
|
5910
|
+
}
|
|
5911
|
+
return true;
|
|
5912
|
+
}
|
|
5224
5913
|
/** Handle a single ParsedEvent from any runtime driver */
|
|
5225
5914
|
handleParsedEvent(agentId, event, driver) {
|
|
5226
5915
|
const ap = this.agents.get(agentId);
|
|
@@ -5322,7 +6011,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5322
6011
|
this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)");
|
|
5323
6012
|
this.flushPendingTrajectory(agentId);
|
|
5324
6013
|
if (ap) {
|
|
5325
|
-
this.releaseAgentStartPermit(agentId, "initial turn ended");
|
|
5326
6014
|
this.clearGatedInFlightBatch(agentId, ap, "turn_end");
|
|
5327
6015
|
if (event.sessionId) ap.sessionId = event.sessionId;
|
|
5328
6016
|
ap.gatedSteering.outstandingToolUses = 0;
|
|
@@ -5345,7 +6033,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5345
6033
|
}
|
|
5346
6034
|
} else {
|
|
5347
6035
|
ap.isIdle = true;
|
|
5348
|
-
|
|
6036
|
+
if (ap.lastRuntimeError) {
|
|
6037
|
+
this.broadcastActivity(agentId, "error", ap.lastRuntimeError);
|
|
6038
|
+
} else {
|
|
6039
|
+
this.broadcastActivity(agentId, "online", "Idle");
|
|
6040
|
+
}
|
|
5349
6041
|
}
|
|
5350
6042
|
this.endRuntimeTrace(ap, "ok", { outcome: "turn-completed" });
|
|
5351
6043
|
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
@@ -5384,6 +6076,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5384
6076
|
}
|
|
5385
6077
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", { message: event.message });
|
|
5386
6078
|
this.endRuntimeTrace(ap, "error", { outcome: "runtime-error", errorMessage: event.message });
|
|
6079
|
+
if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
|
|
6080
|
+
ap.isIdle = true;
|
|
6081
|
+
ap.pendingNotificationCount = 0;
|
|
6082
|
+
if (ap.notificationTimer) {
|
|
6083
|
+
clearTimeout(ap.notificationTimer);
|
|
6084
|
+
ap.notificationTimer = null;
|
|
6085
|
+
}
|
|
6086
|
+
logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
|
|
6087
|
+
}
|
|
5387
6088
|
}
|
|
5388
6089
|
this.broadcastActivity(agentId, "error", event.message, [
|
|
5389
6090
|
{ kind: "text", text: `Error: ${event.message}` }
|
|
@@ -5430,20 +6131,73 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5430
6131
|
const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
|
|
5431
6132
|
if (encoded) {
|
|
5432
6133
|
ap.process.stdin?.write(encoded + "\n");
|
|
6134
|
+
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
6135
|
+
agentId,
|
|
6136
|
+
runtime: ap.config.runtime,
|
|
6137
|
+
model: ap.config.model,
|
|
6138
|
+
launchId: ap.launchId || void 0,
|
|
6139
|
+
outcome: "written",
|
|
6140
|
+
mode: "busy",
|
|
6141
|
+
pending_notification_count: count,
|
|
6142
|
+
session_id_present: true
|
|
6143
|
+
});
|
|
6144
|
+
} else {
|
|
6145
|
+
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
6146
|
+
agentId,
|
|
6147
|
+
runtime: ap.config.runtime,
|
|
6148
|
+
model: ap.config.model,
|
|
6149
|
+
launchId: ap.launchId || void 0,
|
|
6150
|
+
outcome: "encode_failed",
|
|
6151
|
+
mode: "busy",
|
|
6152
|
+
pending_notification_count: count,
|
|
6153
|
+
session_id_present: true
|
|
6154
|
+
}, "error");
|
|
5433
6155
|
}
|
|
5434
6156
|
}
|
|
5435
6157
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
5436
6158
|
deliverMessagesViaStdin(agentId, ap, messages, mode) {
|
|
5437
6159
|
if (messages.length === 0) return true;
|
|
6160
|
+
const split = this.splitRuntimeProfileControlBatch(messages);
|
|
6161
|
+
if (split.deferredMessages.length > 0) {
|
|
6162
|
+
ap.inbox.unshift(...split.deferredMessages);
|
|
6163
|
+
ap.pendingNotificationCount += split.deferredMessages.length;
|
|
6164
|
+
messages = split.nextMessages;
|
|
6165
|
+
this.recordDaemonTrace("daemon.agent.runtime_profile.split_batch", {
|
|
6166
|
+
agentId,
|
|
6167
|
+
launchId: ap.launchId || void 0,
|
|
6168
|
+
runtime: ap.config.runtime,
|
|
6169
|
+
mode,
|
|
6170
|
+
delivered_control_messages_count: messages.length,
|
|
6171
|
+
deferred_messages_count: split.deferredMessages.length,
|
|
6172
|
+
inbox_count: ap.inbox.length,
|
|
6173
|
+
pending_notification_count: ap.pendingNotificationCount
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
const traceAttrs = {
|
|
6177
|
+
agentId,
|
|
6178
|
+
launchId: ap.launchId || void 0,
|
|
6179
|
+
runtime: ap.config.runtime,
|
|
6180
|
+
model: ap.config.model,
|
|
6181
|
+
mode,
|
|
6182
|
+
messages_count: messages.length,
|
|
6183
|
+
session_id_present: Boolean(ap.sessionId),
|
|
6184
|
+
inbox_count: ap.inbox.length,
|
|
6185
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
6186
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
6187
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
6188
|
+
...this.messagesTraceAttrs(messages)
|
|
6189
|
+
};
|
|
5438
6190
|
const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
|
|
5439
6191
|
|
|
5440
6192
|
${formatIncomingMessage(messages[0], ap.driver)}
|
|
5441
6193
|
|
|
5442
|
-
Respond as appropriate. Complete all your work before stopping
|
|
6194
|
+
Respond as appropriate. Complete all your work before stopping.
|
|
6195
|
+
${RESPONSE_TARGET_HINT}` : `New messages received:
|
|
5443
6196
|
|
|
5444
6197
|
${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n")}
|
|
5445
6198
|
|
|
5446
|
-
Respond as appropriate. Complete all your work before stopping
|
|
6199
|
+
Respond as appropriate. Complete all your work before stopping.
|
|
6200
|
+
${RESPONSE_TARGET_HINT}`);
|
|
5447
6201
|
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
|
|
5448
6202
|
if (!encoded) {
|
|
5449
6203
|
ap.inbox.unshift(...messages);
|
|
@@ -5453,14 +6207,27 @@ Respond as appropriate. Complete all your work before stopping.`);
|
|
|
5453
6207
|
logger.warn(
|
|
5454
6208
|
`[Agent ${agentId}] Failed to encode ${mode} stdin delivery; re-queued ${messages.length === 1 ? "message" : `${messages.length} messages`}`
|
|
5455
6209
|
);
|
|
6210
|
+
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
6211
|
+
...traceAttrs,
|
|
6212
|
+
outcome: "encode_failed",
|
|
6213
|
+
requeued_messages_count: messages.length
|
|
6214
|
+
}, "error");
|
|
5456
6215
|
return false;
|
|
5457
6216
|
}
|
|
5458
6217
|
const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
|
|
5459
6218
|
logger.info(
|
|
5460
6219
|
`[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
|
|
5461
6220
|
);
|
|
6221
|
+
if (this.containsOrdinaryInboxMessage(messages)) {
|
|
6222
|
+
ap.lastRuntimeError = null;
|
|
6223
|
+
}
|
|
5462
6224
|
ap.process.stdin?.write(encoded + "\n");
|
|
5463
6225
|
this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
|
|
6226
|
+
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
6227
|
+
...traceAttrs,
|
|
6228
|
+
outcome: "written",
|
|
6229
|
+
stdin_write_attempted: true
|
|
6230
|
+
});
|
|
5464
6231
|
return true;
|
|
5465
6232
|
}
|
|
5466
6233
|
/** List ONE level of a directory — directories returned without children (lazy-loaded on demand) */
|
|
@@ -5505,6 +6272,16 @@ var systemClock = {
|
|
|
5505
6272
|
clearTimeout: (timer) => clearTimeout(timer)
|
|
5506
6273
|
};
|
|
5507
6274
|
var INBOUND_WATCHDOG_MS = 7e4;
|
|
6275
|
+
function durationMsBucket(ms) {
|
|
6276
|
+
if (ms == null || !Number.isFinite(ms) || ms < 0) return "unknown";
|
|
6277
|
+
if (ms === 0) return "0";
|
|
6278
|
+
if (ms <= 1e3) return "1s";
|
|
6279
|
+
if (ms <= 1e4) return "1s-10s";
|
|
6280
|
+
if (ms <= 3e4) return "10s-30s";
|
|
6281
|
+
if (ms <= 6e4) return "30s-60s";
|
|
6282
|
+
if (ms <= 12e4) return "60s-120s";
|
|
6283
|
+
return "120s+";
|
|
6284
|
+
}
|
|
5508
6285
|
var DaemonConnection = class {
|
|
5509
6286
|
ws = null;
|
|
5510
6287
|
options;
|
|
@@ -5516,6 +6293,8 @@ var DaemonConnection = class {
|
|
|
5516
6293
|
shouldConnect = true;
|
|
5517
6294
|
reconnectAttempt = 0;
|
|
5518
6295
|
lastDroppedSendLogAt = 0;
|
|
6296
|
+
lastInboundAt = null;
|
|
6297
|
+
lastInboundMessageKind = null;
|
|
5519
6298
|
constructor(options) {
|
|
5520
6299
|
this.options = options;
|
|
5521
6300
|
this.clock = options.clock ?? systemClock;
|
|
@@ -5550,6 +6329,10 @@ var DaemonConnection = class {
|
|
|
5550
6329
|
this.lastDroppedSendLogAt = now;
|
|
5551
6330
|
logger.warn(`[Daemon] Dropping outbound message while disconnected: ${msg.type}`);
|
|
5552
6331
|
}
|
|
6332
|
+
this.trace("daemon.connection.outbound_dropped", {
|
|
6333
|
+
message_type: msg.type,
|
|
6334
|
+
ws_ready_state: this.ws?.readyState ?? null
|
|
6335
|
+
});
|
|
5553
6336
|
}
|
|
5554
6337
|
get connected() {
|
|
5555
6338
|
return this.ws?.readyState === WebSocket.OPEN;
|
|
@@ -5563,25 +6346,49 @@ var DaemonConnection = class {
|
|
|
5563
6346
|
if (wsOptions?.agent) {
|
|
5564
6347
|
logger.info("[Daemon] Using configured proxy for WebSocket connection");
|
|
5565
6348
|
}
|
|
6349
|
+
this.trace("daemon.connection.connecting", {
|
|
6350
|
+
reconnect_attempt: this.reconnectAttempt,
|
|
6351
|
+
server_url_present: Boolean(this.options.serverUrl),
|
|
6352
|
+
proxy_present: Boolean(wsOptions?.agent)
|
|
6353
|
+
});
|
|
5566
6354
|
const ws = this.options.wsFactory ? this.options.wsFactory(wsUrl, wsOptions) : new WebSocket(wsUrl, wsOptions);
|
|
5567
6355
|
this.ws = ws;
|
|
5568
6356
|
ws.on("open", () => {
|
|
5569
6357
|
if (this.ws !== ws) return;
|
|
5570
6358
|
if (!this.shouldConnect) return;
|
|
5571
6359
|
logger.info("[Daemon] Connected to server");
|
|
6360
|
+
const priorReconnectAttempt = this.reconnectAttempt;
|
|
5572
6361
|
this.reconnectAttempt = 0;
|
|
5573
6362
|
this.reconnectDelay = this.options.minReconnectDelayMs ?? 1e3;
|
|
6363
|
+
this.markInbound("websocket_open");
|
|
5574
6364
|
this.resetWatchdog();
|
|
6365
|
+
this.trace("daemon.connection.connected", {
|
|
6366
|
+
reconnect_attempt: priorReconnectAttempt,
|
|
6367
|
+
inbound_watchdog_ms: this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS
|
|
6368
|
+
});
|
|
5575
6369
|
this.options.onConnect();
|
|
5576
6370
|
});
|
|
5577
6371
|
ws.on("message", (data) => {
|
|
5578
6372
|
if (this.ws !== ws) return;
|
|
5579
|
-
|
|
6373
|
+
let messageKind = "unknown";
|
|
5580
6374
|
try {
|
|
5581
6375
|
const msg = JSON.parse(data.toString());
|
|
6376
|
+
messageKind = msg.type;
|
|
6377
|
+
this.markInbound(messageKind);
|
|
6378
|
+
this.resetWatchdog();
|
|
6379
|
+
this.trace("daemon.connection.inbound_received", {
|
|
6380
|
+
message_type: messageKind,
|
|
6381
|
+
last_inbound_age_ms_bucket: "0"
|
|
6382
|
+
});
|
|
5582
6383
|
this.options.onMessage(msg);
|
|
5583
6384
|
} catch (err) {
|
|
6385
|
+
this.markInbound("invalid_json");
|
|
6386
|
+
this.resetWatchdog();
|
|
5584
6387
|
logger.error("[Daemon] Invalid message from server", err);
|
|
6388
|
+
this.trace("daemon.connection.invalid_message", {
|
|
6389
|
+
error_class: err instanceof Error ? err.name : typeof err,
|
|
6390
|
+
last_inbound_message_kind: "invalid_json"
|
|
6391
|
+
}, "error");
|
|
5585
6392
|
}
|
|
5586
6393
|
});
|
|
5587
6394
|
ws.on("close", (code, reasonBuffer) => {
|
|
@@ -5592,12 +6399,23 @@ var DaemonConnection = class {
|
|
|
5592
6399
|
logger.warn(
|
|
5593
6400
|
`[Daemon] Disconnected from server (code=${code}, reason=${JSON.stringify(reason)}, reconnecting=${this.shouldConnect})`
|
|
5594
6401
|
);
|
|
6402
|
+
this.trace("daemon.connection.disconnected", {
|
|
6403
|
+
close_code: code,
|
|
6404
|
+
close_reason_present: Boolean(reason),
|
|
6405
|
+
reconnecting: this.shouldConnect,
|
|
6406
|
+
reconnect_attempt: this.reconnectAttempt,
|
|
6407
|
+
last_inbound_message_kind: this.lastInboundMessageKind,
|
|
6408
|
+
last_inbound_age_ms_bucket: this.lastInboundAgeBucket()
|
|
6409
|
+
}, this.shouldConnect ? "cancelled" : "ok");
|
|
5595
6410
|
this.options.onDisconnect();
|
|
5596
6411
|
this.scheduleReconnect();
|
|
5597
6412
|
});
|
|
5598
6413
|
ws.on("error", (err) => {
|
|
5599
6414
|
if (this.ws !== ws) return;
|
|
5600
6415
|
logger.error(`[Daemon] WebSocket error: ${err.message}`);
|
|
6416
|
+
this.trace("daemon.connection.error", {
|
|
6417
|
+
error_class: err.name || "Error"
|
|
6418
|
+
}, "error");
|
|
5601
6419
|
});
|
|
5602
6420
|
}
|
|
5603
6421
|
scheduleReconnect() {
|
|
@@ -5605,6 +6423,10 @@ var DaemonConnection = class {
|
|
|
5605
6423
|
if (this.reconnectTimer) return;
|
|
5606
6424
|
this.reconnectAttempt += 1;
|
|
5607
6425
|
logger.info(`[Daemon] Reconnecting to server in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempt})`);
|
|
6426
|
+
this.trace("daemon.connection.reconnect_scheduled", {
|
|
6427
|
+
reconnect_attempt: this.reconnectAttempt,
|
|
6428
|
+
delay_ms: this.reconnectDelay
|
|
6429
|
+
});
|
|
5608
6430
|
this.reconnectTimer = this.clock.setTimeout(() => {
|
|
5609
6431
|
this.reconnectTimer = null;
|
|
5610
6432
|
this.doConnect();
|
|
@@ -5616,6 +6438,13 @@ var DaemonConnection = class {
|
|
|
5616
6438
|
const ms = this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS;
|
|
5617
6439
|
this.watchdogTimer = this.clock.setTimeout(() => {
|
|
5618
6440
|
logger.warn(`[Daemon] No inbound traffic for ${ms / 1e3}s \u2014 forcing reconnect`);
|
|
6441
|
+
this.trace("daemon.connection.watchdog_timeout", {
|
|
6442
|
+
inbound_watchdog_ms: ms,
|
|
6443
|
+
last_inbound_message_kind: this.lastInboundMessageKind,
|
|
6444
|
+
last_inbound_age_ms_bucket: this.lastInboundAgeBucket(),
|
|
6445
|
+
ws_ready_state: this.ws?.readyState ?? null,
|
|
6446
|
+
reconnecting: this.shouldConnect
|
|
6447
|
+
}, "error");
|
|
5619
6448
|
try {
|
|
5620
6449
|
this.ws?.terminate();
|
|
5621
6450
|
} catch {
|
|
@@ -5628,6 +6457,16 @@ var DaemonConnection = class {
|
|
|
5628
6457
|
this.watchdogTimer = null;
|
|
5629
6458
|
}
|
|
5630
6459
|
}
|
|
6460
|
+
markInbound(messageKind) {
|
|
6461
|
+
this.lastInboundAt = this.clock.now();
|
|
6462
|
+
this.lastInboundMessageKind = messageKind;
|
|
6463
|
+
}
|
|
6464
|
+
lastInboundAgeBucket() {
|
|
6465
|
+
return durationMsBucket(this.lastInboundAt == null ? null : this.clock.now() - this.lastInboundAt);
|
|
6466
|
+
}
|
|
6467
|
+
trace(name, attrs, status = "ok") {
|
|
6468
|
+
this.options.onTraceEvent?.(name, attrs, status);
|
|
6469
|
+
}
|
|
5631
6470
|
};
|
|
5632
6471
|
|
|
5633
6472
|
// src/reminderCache.ts
|
|
@@ -5711,10 +6550,10 @@ var ReminderCache = class {
|
|
|
5711
6550
|
|
|
5712
6551
|
// src/machineLock.ts
|
|
5713
6552
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
5714
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
5715
|
-
import
|
|
6553
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
6554
|
+
import os6 from "os";
|
|
5716
6555
|
import path12 from "path";
|
|
5717
|
-
var DEFAULT_MACHINE_STATE_ROOT = path12.join(
|
|
6556
|
+
var DEFAULT_MACHINE_STATE_ROOT = path12.join(os6.homedir(), ".slock", "machines");
|
|
5718
6557
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
5719
6558
|
var DaemonMachineLockConflictError = class extends Error {
|
|
5720
6559
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -5737,14 +6576,14 @@ function ownerPath(lockDir) {
|
|
|
5737
6576
|
}
|
|
5738
6577
|
function readOwner(lockDir) {
|
|
5739
6578
|
try {
|
|
5740
|
-
return JSON.parse(
|
|
6579
|
+
return JSON.parse(readFileSync5(ownerPath(lockDir), "utf8"));
|
|
5741
6580
|
} catch {
|
|
5742
6581
|
return null;
|
|
5743
6582
|
}
|
|
5744
6583
|
}
|
|
5745
6584
|
function lockAgeMs(lockDir) {
|
|
5746
6585
|
try {
|
|
5747
|
-
return Date.now() -
|
|
6586
|
+
return Date.now() - statSync3(lockDir).mtimeMs;
|
|
5748
6587
|
} catch {
|
|
5749
6588
|
return null;
|
|
5750
6589
|
}
|
|
@@ -5773,7 +6612,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
5773
6612
|
const owner = {
|
|
5774
6613
|
pid: process.pid,
|
|
5775
6614
|
token,
|
|
5776
|
-
hostname:
|
|
6615
|
+
hostname: os6.hostname(),
|
|
5777
6616
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5778
6617
|
serverUrl: options.serverUrl,
|
|
5779
6618
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
@@ -5815,6 +6654,418 @@ function acquireDaemonMachineLock(options) {
|
|
|
5815
6654
|
throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
|
|
5816
6655
|
}
|
|
5817
6656
|
|
|
6657
|
+
// src/localTraceSink.ts
|
|
6658
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
6659
|
+
import path13 from "path";
|
|
6660
|
+
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6661
|
+
var DEFAULT_MAX_FILES = 8;
|
|
6662
|
+
var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
|
|
6663
|
+
"serverId",
|
|
6664
|
+
"machineId",
|
|
6665
|
+
"agentId",
|
|
6666
|
+
"messageId",
|
|
6667
|
+
"launchId",
|
|
6668
|
+
"uploadId",
|
|
6669
|
+
"bundleId",
|
|
6670
|
+
"deliveryId",
|
|
6671
|
+
"deliveryCorrelationId",
|
|
6672
|
+
"delivery_correlation_id"
|
|
6673
|
+
]);
|
|
6674
|
+
var LocalRotatingTraceSink = class {
|
|
6675
|
+
traceDir;
|
|
6676
|
+
maxFileBytes;
|
|
6677
|
+
maxFiles;
|
|
6678
|
+
currentFile = null;
|
|
6679
|
+
currentSize = 0;
|
|
6680
|
+
sequence = 0;
|
|
6681
|
+
constructor(options) {
|
|
6682
|
+
this.traceDir = path13.join(options.machineDir, "traces");
|
|
6683
|
+
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
6684
|
+
this.maxFiles = Math.max(1, Math.floor(options.maxFiles ?? DEFAULT_MAX_FILES));
|
|
6685
|
+
}
|
|
6686
|
+
record(span) {
|
|
6687
|
+
try {
|
|
6688
|
+
const line = `${JSON.stringify(toLocalTraceRecord(span))}
|
|
6689
|
+
`;
|
|
6690
|
+
this.ensureFile(Buffer.byteLength(line));
|
|
6691
|
+
appendFileSync(this.currentFile, line, { encoding: "utf8" });
|
|
6692
|
+
this.currentSize += Buffer.byteLength(line);
|
|
6693
|
+
} catch {
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
getCurrentFile() {
|
|
6697
|
+
return this.currentFile;
|
|
6698
|
+
}
|
|
6699
|
+
ensureFile(nextBytes) {
|
|
6700
|
+
mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
|
|
6701
|
+
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes) {
|
|
6702
|
+
this.currentFile = path13.join(
|
|
6703
|
+
this.traceDir,
|
|
6704
|
+
`daemon-trace-${safeTimestamp(Date.now())}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
6705
|
+
);
|
|
6706
|
+
writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
|
|
6707
|
+
this.currentSize = statSync4(this.currentFile).size;
|
|
6708
|
+
this.pruneOldFiles();
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6711
|
+
pruneOldFiles() {
|
|
6712
|
+
const files = readdirSync3(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
|
|
6713
|
+
const excess = files.length - this.maxFiles;
|
|
6714
|
+
if (excess <= 0) return;
|
|
6715
|
+
for (const file of files.slice(0, excess)) {
|
|
6716
|
+
rmSync3(path13.join(this.traceDir, file), { force: true });
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6719
|
+
};
|
|
6720
|
+
function safeTimestamp(timeMs) {
|
|
6721
|
+
return new Date(timeMs).toISOString().replace(/[:.]/g, "-");
|
|
6722
|
+
}
|
|
6723
|
+
function toLocalTraceRecord(span) {
|
|
6724
|
+
return {
|
|
6725
|
+
type: "span",
|
|
6726
|
+
schema_version: 1,
|
|
6727
|
+
trace_id: span.context.traceId,
|
|
6728
|
+
span_id: span.context.spanId,
|
|
6729
|
+
parent_span_id: span.context.parentSpanId,
|
|
6730
|
+
name: span.name,
|
|
6731
|
+
surface: span.surface,
|
|
6732
|
+
kind: span.kind,
|
|
6733
|
+
status: span.status,
|
|
6734
|
+
start_time: new Date(span.startTimeMs).toISOString(),
|
|
6735
|
+
end_time: new Date(span.endTimeMs).toISOString(),
|
|
6736
|
+
duration_ms: span.durationMs,
|
|
6737
|
+
attrs: sanitizeAttrs(span.attrs),
|
|
6738
|
+
events: span.events.map(sanitizeEvent)
|
|
6739
|
+
};
|
|
6740
|
+
}
|
|
6741
|
+
function sanitizeEvent(event) {
|
|
6742
|
+
return {
|
|
6743
|
+
name: event.name,
|
|
6744
|
+
time: new Date(event.timeMs).toISOString(),
|
|
6745
|
+
attrs: sanitizeAttrs(event.attrs)
|
|
6746
|
+
};
|
|
6747
|
+
}
|
|
6748
|
+
function sanitizeAttrs(attrs) {
|
|
6749
|
+
if (!attrs) return void 0;
|
|
6750
|
+
const sanitized = {};
|
|
6751
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
6752
|
+
if (isDiagnosticIdAttr(key)) {
|
|
6753
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
6754
|
+
sanitized[key] = sanitizeValue(value);
|
|
6755
|
+
continue;
|
|
6756
|
+
}
|
|
6757
|
+
if (shouldDropAttr(key)) continue;
|
|
6758
|
+
sanitized[key] = sanitizeValue(value);
|
|
6759
|
+
}
|
|
6760
|
+
return sanitized;
|
|
6761
|
+
}
|
|
6762
|
+
function sanitizeValue(value) {
|
|
6763
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
6764
|
+
return value;
|
|
6765
|
+
}
|
|
6766
|
+
if (Array.isArray(value)) {
|
|
6767
|
+
return { items_count: value.length };
|
|
6768
|
+
}
|
|
6769
|
+
if (typeof value === "object") {
|
|
6770
|
+
return { object_present: true };
|
|
6771
|
+
}
|
|
6772
|
+
return String(value);
|
|
6773
|
+
}
|
|
6774
|
+
function shouldDropAttr(key) {
|
|
6775
|
+
const normalized = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
6776
|
+
if (/(^|_)(api_key|auth_token|token|secret|password|cookie|credential)(_|$)/i.test(normalized)) {
|
|
6777
|
+
return true;
|
|
6778
|
+
}
|
|
6779
|
+
if (/(^|_)(count|present|kind|mode|source|outcome|reason|class|status|bucket|ms|code|truncated)$/.test(normalized)) {
|
|
6780
|
+
return false;
|
|
6781
|
+
}
|
|
6782
|
+
if (/(^|_)id$/.test(normalized)) {
|
|
6783
|
+
return true;
|
|
6784
|
+
}
|
|
6785
|
+
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);
|
|
6786
|
+
}
|
|
6787
|
+
function isDiagnosticIdAttr(key) {
|
|
6788
|
+
return DIAGNOSTIC_ID_ATTRS.has(key);
|
|
6789
|
+
}
|
|
6790
|
+
|
|
6791
|
+
// src/traceBundleUpload.ts
|
|
6792
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
6793
|
+
import { gzipSync } from "zlib";
|
|
6794
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
6795
|
+
import path14 from "path";
|
|
6796
|
+
|
|
6797
|
+
// src/directUploadCapability.ts
|
|
6798
|
+
function joinUrl(base, path16) {
|
|
6799
|
+
return `${base.replace(/\/+$/, "")}${path16}`;
|
|
6800
|
+
}
|
|
6801
|
+
function jsonHeaders(apiKey) {
|
|
6802
|
+
return {
|
|
6803
|
+
"Content-Type": "application/json",
|
|
6804
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
6805
|
+
};
|
|
6806
|
+
}
|
|
6807
|
+
async function requestDaemonScopeAttestation({
|
|
6808
|
+
serverUrl,
|
|
6809
|
+
apiKey,
|
|
6810
|
+
scope,
|
|
6811
|
+
metadata,
|
|
6812
|
+
fetchImpl = fetch,
|
|
6813
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
|
|
6814
|
+
}) {
|
|
6815
|
+
const { response, data } = await executeJsonRequest(
|
|
6816
|
+
joinUrl(serverUrl, "/internal/machine/scope-attestation"),
|
|
6817
|
+
{
|
|
6818
|
+
method: "POST",
|
|
6819
|
+
headers: jsonHeaders(apiKey),
|
|
6820
|
+
body: JSON.stringify({
|
|
6821
|
+
scope,
|
|
6822
|
+
...metadata ? { metadata } : {}
|
|
6823
|
+
})
|
|
6824
|
+
},
|
|
6825
|
+
{
|
|
6826
|
+
toolName: "daemon_direct_upload.scope_attestation",
|
|
6827
|
+
target: scope,
|
|
6828
|
+
timeoutMs,
|
|
6829
|
+
fetchImpl
|
|
6830
|
+
}
|
|
6831
|
+
);
|
|
6832
|
+
if (!response.ok) {
|
|
6833
|
+
throw new Error(`Failed to request daemon scope attestation (${response.status})`);
|
|
6834
|
+
}
|
|
6835
|
+
return data;
|
|
6836
|
+
}
|
|
6837
|
+
async function createDirectUploadSession({
|
|
6838
|
+
serverUrl,
|
|
6839
|
+
apiKey,
|
|
6840
|
+
workerUrl,
|
|
6841
|
+
scope,
|
|
6842
|
+
createPath = "/api/uploads",
|
|
6843
|
+
body,
|
|
6844
|
+
attestationMetadata,
|
|
6845
|
+
fetchImpl = fetch,
|
|
6846
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
|
|
6847
|
+
}) {
|
|
6848
|
+
const capability = await requestDaemonScopeAttestation({
|
|
6849
|
+
serverUrl,
|
|
6850
|
+
apiKey,
|
|
6851
|
+
scope,
|
|
6852
|
+
metadata: attestationMetadata,
|
|
6853
|
+
fetchImpl,
|
|
6854
|
+
timeoutMs
|
|
6855
|
+
});
|
|
6856
|
+
const { response, data } = await executeJsonRequest(
|
|
6857
|
+
joinUrl(workerUrl, createPath),
|
|
6858
|
+
{
|
|
6859
|
+
method: "POST",
|
|
6860
|
+
headers: jsonHeaders(),
|
|
6861
|
+
body: JSON.stringify({
|
|
6862
|
+
...body,
|
|
6863
|
+
attestation: capability.attestation
|
|
6864
|
+
})
|
|
6865
|
+
},
|
|
6866
|
+
{
|
|
6867
|
+
toolName: "daemon_direct_upload.create",
|
|
6868
|
+
target: capability.audience,
|
|
6869
|
+
timeoutMs,
|
|
6870
|
+
fetchImpl
|
|
6871
|
+
}
|
|
6872
|
+
);
|
|
6873
|
+
if (!response.ok) {
|
|
6874
|
+
throw new Error(`Failed to create direct upload session (${response.status})`);
|
|
6875
|
+
}
|
|
6876
|
+
return { capability, response: data };
|
|
6877
|
+
}
|
|
6878
|
+
async function uploadWithSignedCapability({
|
|
6879
|
+
serverUrl,
|
|
6880
|
+
apiKey,
|
|
6881
|
+
workerUrl,
|
|
6882
|
+
scope,
|
|
6883
|
+
createPath = "/api/uploads",
|
|
6884
|
+
createBody,
|
|
6885
|
+
attestationMetadata,
|
|
6886
|
+
uploadBody,
|
|
6887
|
+
fetchImpl = fetch,
|
|
6888
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
|
|
6889
|
+
}) {
|
|
6890
|
+
const { capability, response: session } = await createDirectUploadSession({
|
|
6891
|
+
serverUrl,
|
|
6892
|
+
apiKey,
|
|
6893
|
+
workerUrl,
|
|
6894
|
+
scope,
|
|
6895
|
+
createPath,
|
|
6896
|
+
body: createBody,
|
|
6897
|
+
attestationMetadata,
|
|
6898
|
+
fetchImpl,
|
|
6899
|
+
timeoutMs
|
|
6900
|
+
});
|
|
6901
|
+
const { response: uploadResponse } = await executeResponseRequest(
|
|
6902
|
+
session.upload.url,
|
|
6903
|
+
{
|
|
6904
|
+
method: session.upload.method,
|
|
6905
|
+
headers: session.upload.headers ?? {},
|
|
6906
|
+
body: uploadBody
|
|
6907
|
+
},
|
|
6908
|
+
{
|
|
6909
|
+
toolName: "daemon_direct_upload.put",
|
|
6910
|
+
target: capability.audience,
|
|
6911
|
+
timeoutMs,
|
|
6912
|
+
fetchImpl
|
|
6913
|
+
}
|
|
6914
|
+
);
|
|
6915
|
+
if (!uploadResponse.ok) {
|
|
6916
|
+
throw new Error(`Failed to upload with signed capability (${uploadResponse.status})`);
|
|
6917
|
+
}
|
|
6918
|
+
return { capability, session, uploadResponse };
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
// src/traceBundleUpload.ts
|
|
6922
|
+
var TRACE_UPLOAD_SCOPE = "daemon-trace-bundle:create";
|
|
6923
|
+
var DEFAULT_UPLOAD_INTERVAL_MS = 5 * 60 * 1e3;
|
|
6924
|
+
var DEFAULT_MIN_FILE_AGE_MS = 60 * 1e3;
|
|
6925
|
+
var DEFAULT_MAX_FILES_PER_RUN = 4;
|
|
6926
|
+
var DaemonTraceBundleUploader = class {
|
|
6927
|
+
options;
|
|
6928
|
+
timer = null;
|
|
6929
|
+
constructor(options) {
|
|
6930
|
+
this.options = options;
|
|
6931
|
+
}
|
|
6932
|
+
start() {
|
|
6933
|
+
if (this.timer) return;
|
|
6934
|
+
void this.uploadOnce();
|
|
6935
|
+
this.timer = setInterval(() => {
|
|
6936
|
+
void this.uploadOnce();
|
|
6937
|
+
}, this.options.intervalMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_INTERVAL_MS", DEFAULT_UPLOAD_INTERVAL_MS));
|
|
6938
|
+
}
|
|
6939
|
+
stop() {
|
|
6940
|
+
if (!this.timer) return;
|
|
6941
|
+
clearInterval(this.timer);
|
|
6942
|
+
this.timer = null;
|
|
6943
|
+
}
|
|
6944
|
+
async uploadOnce() {
|
|
6945
|
+
const files = await this.findUploadCandidates();
|
|
6946
|
+
let uploaded = 0;
|
|
6947
|
+
for (const file of files.slice(0, this.options.maxFilesPerRun ?? DEFAULT_MAX_FILES_PER_RUN)) {
|
|
6948
|
+
if (await this.uploadFile(file)) uploaded += 1;
|
|
6949
|
+
}
|
|
6950
|
+
return { attempted: files.length, uploaded };
|
|
6951
|
+
}
|
|
6952
|
+
async findUploadCandidates() {
|
|
6953
|
+
const traceDir = path14.join(this.options.machineDir, "traces");
|
|
6954
|
+
let names;
|
|
6955
|
+
try {
|
|
6956
|
+
names = await readdir3(traceDir);
|
|
6957
|
+
} catch {
|
|
6958
|
+
return [];
|
|
6959
|
+
}
|
|
6960
|
+
const now = Date.now();
|
|
6961
|
+
const minAgeMs = this.options.minFileAgeMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_MIN_FILE_AGE_MS", DEFAULT_MIN_FILE_AGE_MS);
|
|
6962
|
+
const currentFile = this.options.currentFileProvider?.();
|
|
6963
|
+
const candidates = [];
|
|
6964
|
+
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
6965
|
+
const file = path14.join(traceDir, name);
|
|
6966
|
+
if (currentFile && path14.resolve(file) === path14.resolve(currentFile)) continue;
|
|
6967
|
+
if (await this.isUploaded(file)) continue;
|
|
6968
|
+
try {
|
|
6969
|
+
const info = await stat3(file);
|
|
6970
|
+
if (!info.isFile() || info.size <= 0) continue;
|
|
6971
|
+
if (now - info.mtimeMs < minAgeMs) continue;
|
|
6972
|
+
candidates.push(file);
|
|
6973
|
+
} catch {
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
return candidates;
|
|
6977
|
+
}
|
|
6978
|
+
async uploadFile(file) {
|
|
6979
|
+
const span = this.options.tracer?.startSpan("daemon.bundle.upload", {
|
|
6980
|
+
surface: "daemon",
|
|
6981
|
+
kind: "producer",
|
|
6982
|
+
attrs: {
|
|
6983
|
+
file_present: true,
|
|
6984
|
+
worker_url_present: Boolean(this.options.workerUrl)
|
|
6985
|
+
}
|
|
6986
|
+
});
|
|
6987
|
+
try {
|
|
6988
|
+
const raw = await readFile2(file);
|
|
6989
|
+
if (raw.byteLength === 0) {
|
|
6990
|
+
span?.end("cancelled", { attrs: { outcome: "empty" } });
|
|
6991
|
+
return false;
|
|
6992
|
+
}
|
|
6993
|
+
const gzipped = gzipSync(raw);
|
|
6994
|
+
const bundleSha256 = sha256Hex(gzipped);
|
|
6995
|
+
const bundleId = randomUUID3();
|
|
6996
|
+
await uploadWithSignedCapability({
|
|
6997
|
+
serverUrl: this.options.serverUrl,
|
|
6998
|
+
apiKey: this.options.apiKey,
|
|
6999
|
+
workerUrl: this.options.workerUrl,
|
|
7000
|
+
scope: TRACE_UPLOAD_SCOPE,
|
|
7001
|
+
createPath: "/api/trace-bundles",
|
|
7002
|
+
attestationMetadata: {
|
|
7003
|
+
bundleId,
|
|
7004
|
+
bundleSha256,
|
|
7005
|
+
bundleSizeBytes: gzipped.byteLength
|
|
7006
|
+
},
|
|
7007
|
+
createBody: {
|
|
7008
|
+
bundleSha256,
|
|
7009
|
+
bundleSizeBytes: gzipped.byteLength
|
|
7010
|
+
},
|
|
7011
|
+
uploadBody: new Blob([new Uint8Array(gzipped)], { type: "application/x-ndjson" }),
|
|
7012
|
+
fetchImpl: this.options.fetchImpl
|
|
7013
|
+
});
|
|
7014
|
+
await this.markUploaded(file, {
|
|
7015
|
+
bundleId,
|
|
7016
|
+
bundleSha256,
|
|
7017
|
+
bundleSizeBytes: gzipped.byteLength
|
|
7018
|
+
});
|
|
7019
|
+
span?.end("ok", {
|
|
7020
|
+
attrs: {
|
|
7021
|
+
bundleId,
|
|
7022
|
+
bundle_size_bytes: gzipped.byteLength
|
|
7023
|
+
}
|
|
7024
|
+
});
|
|
7025
|
+
return true;
|
|
7026
|
+
} catch (err) {
|
|
7027
|
+
span?.end("error", {
|
|
7028
|
+
attrs: {
|
|
7029
|
+
error_class: err instanceof Error ? err.name : "Error",
|
|
7030
|
+
error_message_present: err instanceof Error && Boolean(err.message)
|
|
7031
|
+
}
|
|
7032
|
+
});
|
|
7033
|
+
return false;
|
|
7034
|
+
}
|
|
7035
|
+
}
|
|
7036
|
+
uploadStatePath(file) {
|
|
7037
|
+
const stateDir = path14.join(this.options.machineDir, "trace-uploads");
|
|
7038
|
+
return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
|
|
7039
|
+
}
|
|
7040
|
+
async isUploaded(file) {
|
|
7041
|
+
try {
|
|
7042
|
+
await stat3(this.uploadStatePath(file));
|
|
7043
|
+
return true;
|
|
7044
|
+
} catch {
|
|
7045
|
+
return false;
|
|
7046
|
+
}
|
|
7047
|
+
}
|
|
7048
|
+
async markUploaded(file, metadata) {
|
|
7049
|
+
const stateFile = this.uploadStatePath(file);
|
|
7050
|
+
await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
|
|
7051
|
+
await writeFile2(stateFile, `${JSON.stringify({
|
|
7052
|
+
file: path14.basename(file),
|
|
7053
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7054
|
+
...metadata
|
|
7055
|
+
}, null, 2)}
|
|
7056
|
+
`, { mode: 384 });
|
|
7057
|
+
}
|
|
7058
|
+
};
|
|
7059
|
+
function sha256Hex(body) {
|
|
7060
|
+
return createHash2("sha256").update(body).digest("hex");
|
|
7061
|
+
}
|
|
7062
|
+
function readPositiveIntegerEnv2(name, fallback) {
|
|
7063
|
+
const value = process.env[name];
|
|
7064
|
+
if (!value) return fallback;
|
|
7065
|
+
const parsed = Number(value);
|
|
7066
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
7067
|
+
}
|
|
7068
|
+
|
|
5818
7069
|
// src/core.ts
|
|
5819
7070
|
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
|
|
5820
7071
|
function parseDaemonCliArgs(args) {
|
|
@@ -5836,59 +7087,110 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
5836
7087
|
}
|
|
5837
7088
|
}
|
|
5838
7089
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
5839
|
-
const dirname =
|
|
5840
|
-
const jsPath =
|
|
7090
|
+
const dirname = path15.dirname(fileURLToPath(moduleUrl));
|
|
7091
|
+
const jsPath = path15.resolve(dirname, "chat-bridge.js");
|
|
5841
7092
|
try {
|
|
5842
7093
|
accessSync(jsPath);
|
|
5843
7094
|
return jsPath;
|
|
5844
7095
|
} catch {
|
|
5845
|
-
return
|
|
7096
|
+
return path15.resolve(dirname, "chat-bridge.ts");
|
|
5846
7097
|
}
|
|
5847
7098
|
}
|
|
5848
7099
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
5849
|
-
const thisDir =
|
|
5850
|
-
const bundledDistPath =
|
|
7100
|
+
const thisDir = path15.dirname(fileURLToPath(moduleUrl));
|
|
7101
|
+
const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
|
|
5851
7102
|
try {
|
|
5852
7103
|
accessSync(bundledDistPath);
|
|
5853
7104
|
return bundledDistPath;
|
|
5854
7105
|
} catch {
|
|
5855
|
-
const workspaceDistPath =
|
|
7106
|
+
const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
5856
7107
|
accessSync(workspaceDistPath);
|
|
5857
7108
|
return workspaceDistPath;
|
|
5858
7109
|
}
|
|
5859
7110
|
}
|
|
5860
|
-
function detectRuntimes() {
|
|
7111
|
+
function detectRuntimes(tracer = noopTracer) {
|
|
5861
7112
|
const ids = [];
|
|
5862
7113
|
const versions = {};
|
|
7114
|
+
const span = tracer.startSpan("daemon.runtime.detect", {
|
|
7115
|
+
surface: "daemon",
|
|
7116
|
+
kind: "internal",
|
|
7117
|
+
attrs: {
|
|
7118
|
+
known_runtime_count: RUNTIMES.length
|
|
7119
|
+
}
|
|
7120
|
+
});
|
|
5863
7121
|
for (const runtime of RUNTIMES) {
|
|
5864
7122
|
const driver = getDriver(runtime.id);
|
|
7123
|
+
let probeErrorPresent = false;
|
|
5865
7124
|
try {
|
|
5866
7125
|
if (driver.probe) {
|
|
5867
7126
|
const probe = driver.probe();
|
|
5868
7127
|
if (!probe.available) {
|
|
5869
7128
|
if (probe.version) versions[runtime.id] = probe.version;
|
|
7129
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7130
|
+
runtime: runtime.id,
|
|
7131
|
+
outcome: "unavailable",
|
|
7132
|
+
version_present: Boolean(probe.version),
|
|
7133
|
+
binary_path_present: false
|
|
7134
|
+
});
|
|
5870
7135
|
continue;
|
|
5871
7136
|
}
|
|
5872
7137
|
ids.push(runtime.id);
|
|
5873
7138
|
if (probe.version) versions[runtime.id] = probe.version;
|
|
7139
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7140
|
+
runtime: runtime.id,
|
|
7141
|
+
outcome: "available",
|
|
7142
|
+
version_present: Boolean(probe.version),
|
|
7143
|
+
binary_path_present: false
|
|
7144
|
+
});
|
|
5874
7145
|
continue;
|
|
5875
7146
|
}
|
|
5876
7147
|
} catch {
|
|
7148
|
+
probeErrorPresent = true;
|
|
5877
7149
|
}
|
|
5878
7150
|
const detectionBinaries = [runtime.binary];
|
|
7151
|
+
let detectedByPath = false;
|
|
5879
7152
|
for (const binary of detectionBinaries) {
|
|
5880
7153
|
const resolved = resolveCommandOnPath(binary);
|
|
5881
7154
|
if (!resolved) continue;
|
|
5882
7155
|
ids.push(runtime.id);
|
|
7156
|
+
detectedByPath = true;
|
|
5883
7157
|
const version = readCommandVersion(binary);
|
|
5884
7158
|
if (version) {
|
|
5885
7159
|
versions[runtime.id] = version;
|
|
5886
7160
|
}
|
|
7161
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7162
|
+
runtime: runtime.id,
|
|
7163
|
+
outcome: "available",
|
|
7164
|
+
version_present: Boolean(version),
|
|
7165
|
+
binary_path_present: true,
|
|
7166
|
+
probe_error_present: probeErrorPresent
|
|
7167
|
+
});
|
|
5887
7168
|
break;
|
|
5888
7169
|
}
|
|
7170
|
+
if (!detectedByPath) {
|
|
7171
|
+
span.addEvent("daemon.runtime.detect.checked", {
|
|
7172
|
+
runtime: runtime.id,
|
|
7173
|
+
outcome: "unavailable",
|
|
7174
|
+
version_present: false,
|
|
7175
|
+
binary_path_present: false,
|
|
7176
|
+
probe_error_present: probeErrorPresent
|
|
7177
|
+
});
|
|
7178
|
+
}
|
|
5889
7179
|
}
|
|
7180
|
+
span.end("ok", {
|
|
7181
|
+
attrs: {
|
|
7182
|
+
detected_runtime_count: ids.length
|
|
7183
|
+
}
|
|
7184
|
+
});
|
|
5890
7185
|
return { ids, versions };
|
|
5891
7186
|
}
|
|
7187
|
+
function readPositiveIntegerEnv3(name, fallback) {
|
|
7188
|
+
const raw = process.env[name];
|
|
7189
|
+
if (!raw) return fallback;
|
|
7190
|
+
const parsed = Number(raw);
|
|
7191
|
+
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
7192
|
+
return Math.floor(parsed);
|
|
7193
|
+
}
|
|
5892
7194
|
function formatChannelTarget(msg) {
|
|
5893
7195
|
return msg.message.channel_type === "dm" ? `dm:@${msg.message.channel_name}` : `#${msg.message.channel_name}`;
|
|
5894
7196
|
}
|
|
@@ -5938,14 +7240,18 @@ var DaemonCore = class {
|
|
|
5938
7240
|
connection;
|
|
5939
7241
|
reminderCache;
|
|
5940
7242
|
tracer;
|
|
7243
|
+
injectedTracer;
|
|
5941
7244
|
machineLock = null;
|
|
7245
|
+
localTraceSink = null;
|
|
7246
|
+
traceBundleUploader = null;
|
|
5942
7247
|
constructor(options) {
|
|
5943
7248
|
this.options = options;
|
|
5944
7249
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
5945
7250
|
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
5946
7251
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
5947
|
-
this.
|
|
7252
|
+
this.injectedTracer = Boolean(options.tracer);
|
|
5948
7253
|
this.tracer = options.tracer ?? noopTracer;
|
|
7254
|
+
this.runtimeDetector = options.runtimeDetector ?? (() => detectRuntimes(this.tracer));
|
|
5949
7255
|
this.reminderCache = new ReminderCache({
|
|
5950
7256
|
clock: options.reminderClock,
|
|
5951
7257
|
onFire: (job) => this.onReminderFire(job)
|
|
@@ -5955,7 +7261,8 @@ var DaemonCore = class {
|
|
|
5955
7261
|
dataDir: options.dataDir,
|
|
5956
7262
|
serverUrl: options.serverUrl,
|
|
5957
7263
|
defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
|
|
5958
|
-
slockCliPath: this.slockCliPath
|
|
7264
|
+
slockCliPath: this.slockCliPath,
|
|
7265
|
+
tracer: this.tracer
|
|
5959
7266
|
};
|
|
5960
7267
|
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
7268
|
const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
|
|
@@ -5965,15 +7272,48 @@ var DaemonCore = class {
|
|
|
5965
7272
|
...options.connectionOptions,
|
|
5966
7273
|
onMessage: (msg) => this.handleMessage(msg),
|
|
5967
7274
|
onConnect: () => this.handleConnect(),
|
|
5968
|
-
onDisconnect: () => this.handleDisconnect()
|
|
7275
|
+
onDisconnect: () => this.handleDisconnect(),
|
|
7276
|
+
onTraceEvent: (name, attrs, status) => this.recordDaemonTrace(name, attrs, status)
|
|
5969
7277
|
});
|
|
5970
7278
|
this.connection = connection;
|
|
5971
7279
|
}
|
|
5972
7280
|
resolveMachineStateRoot() {
|
|
5973
7281
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
5974
|
-
if (this.options.dataDir) return
|
|
7282
|
+
if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
|
|
5975
7283
|
return DEFAULT_MACHINE_STATE_ROOT;
|
|
5976
7284
|
}
|
|
7285
|
+
shouldEnableLocalTrace() {
|
|
7286
|
+
if (this.injectedTracer) return false;
|
|
7287
|
+
if (!this.options.localTrace) return false;
|
|
7288
|
+
return process.env.SLOCK_DAEMON_LOCAL_TRACE !== "0";
|
|
7289
|
+
}
|
|
7290
|
+
installLocalTraceSink(machineDir) {
|
|
7291
|
+
if (!this.shouldEnableLocalTrace()) return;
|
|
7292
|
+
this.localTraceSink = new LocalRotatingTraceSink({
|
|
7293
|
+
machineDir,
|
|
7294
|
+
maxFileBytes: this.options.localTraceMaxFileBytes ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_BYTES", 5 * 1024 * 1024),
|
|
7295
|
+
maxFiles: this.options.localTraceMaxFiles ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILES", 8)
|
|
7296
|
+
});
|
|
7297
|
+
this.tracer = new BasicTracer({
|
|
7298
|
+
sink: this.localTraceSink
|
|
7299
|
+
});
|
|
7300
|
+
this.agentManager.setTracer(this.tracer);
|
|
7301
|
+
}
|
|
7302
|
+
installTraceBundleUploader(machineDir) {
|
|
7303
|
+
if (!this.shouldEnableLocalTrace()) return;
|
|
7304
|
+
if (this.traceBundleUploader) return;
|
|
7305
|
+
const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL;
|
|
7306
|
+
if (!workerUrl) return;
|
|
7307
|
+
this.traceBundleUploader = new DaemonTraceBundleUploader({
|
|
7308
|
+
machineDir,
|
|
7309
|
+
serverUrl: this.options.serverUrl,
|
|
7310
|
+
apiKey: this.options.apiKey,
|
|
7311
|
+
workerUrl,
|
|
7312
|
+
tracer: this.tracer,
|
|
7313
|
+
currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null
|
|
7314
|
+
});
|
|
7315
|
+
this.traceBundleUploader.start();
|
|
7316
|
+
}
|
|
5977
7317
|
start() {
|
|
5978
7318
|
logger.info("[Slock Daemon] Starting...");
|
|
5979
7319
|
if (!this.machineLock) {
|
|
@@ -5983,10 +7323,24 @@ var DaemonCore = class {
|
|
|
5983
7323
|
rootDir: this.resolveMachineStateRoot()
|
|
5984
7324
|
});
|
|
5985
7325
|
logger.info(`[Slock Daemon] Acquired machine lock: ${this.machineLock.lockDir}`);
|
|
7326
|
+
this.installLocalTraceSink(this.machineLock.machineDir);
|
|
7327
|
+
this.installTraceBundleUploader(this.machineLock.machineDir);
|
|
7328
|
+
const span = this.tracer.startSpan("daemon.lifecycle.start", {
|
|
7329
|
+
surface: "daemon",
|
|
7330
|
+
kind: "internal",
|
|
7331
|
+
attrs: {
|
|
7332
|
+
machine_dir_present: true,
|
|
7333
|
+
local_trace_enabled: this.shouldEnableLocalTrace()
|
|
7334
|
+
}
|
|
7335
|
+
});
|
|
7336
|
+
span.addEvent("daemon.machine_lock.acquired", { machine_dir_present: true });
|
|
7337
|
+
span.end("ok");
|
|
5986
7338
|
}
|
|
5987
7339
|
try {
|
|
5988
7340
|
this.connection.connect();
|
|
5989
7341
|
} catch (err) {
|
|
7342
|
+
this.traceBundleUploader?.stop();
|
|
7343
|
+
this.traceBundleUploader = null;
|
|
5990
7344
|
this.machineLock.release();
|
|
5991
7345
|
this.machineLock = null;
|
|
5992
7346
|
throw err;
|
|
@@ -5994,13 +7348,24 @@ var DaemonCore = class {
|
|
|
5994
7348
|
}
|
|
5995
7349
|
async stop() {
|
|
5996
7350
|
logger.info("[Slock Daemon] Shutting down...");
|
|
7351
|
+
const span = this.tracer.startSpan("daemon.lifecycle.stop", {
|
|
7352
|
+
surface: "daemon",
|
|
7353
|
+
kind: "internal",
|
|
7354
|
+
attrs: { machine_lock_present: Boolean(this.machineLock) }
|
|
7355
|
+
});
|
|
5997
7356
|
this.reminderCache.clear();
|
|
7357
|
+
this.traceBundleUploader?.stop();
|
|
7358
|
+
this.traceBundleUploader = null;
|
|
5998
7359
|
try {
|
|
5999
7360
|
await this.agentManager.stopAll();
|
|
7361
|
+
span.addEvent("daemon.agents.stopped");
|
|
6000
7362
|
} finally {
|
|
6001
7363
|
this.connection.disconnect();
|
|
7364
|
+
span.addEvent("daemon.connection.disconnect_requested");
|
|
6002
7365
|
this.machineLock?.release();
|
|
7366
|
+
if (this.machineLock) span.addEvent("daemon.machine_lock.released");
|
|
6003
7367
|
this.machineLock = null;
|
|
7368
|
+
span.end("ok");
|
|
6004
7369
|
}
|
|
6005
7370
|
}
|
|
6006
7371
|
get connected() {
|
|
@@ -6009,6 +7374,14 @@ var DaemonCore = class {
|
|
|
6009
7374
|
getRunningAgentIds() {
|
|
6010
7375
|
return this.agentManager.getRunningAgentIds();
|
|
6011
7376
|
}
|
|
7377
|
+
recordDaemonTrace(name, attrs, status = "ok") {
|
|
7378
|
+
const span = this.tracer.startSpan(name, {
|
|
7379
|
+
surface: "daemon",
|
|
7380
|
+
kind: "internal",
|
|
7381
|
+
attrs
|
|
7382
|
+
});
|
|
7383
|
+
span.end(status);
|
|
7384
|
+
}
|
|
6012
7385
|
handleMessage(msg) {
|
|
6013
7386
|
const summary = summarizeIncomingMessage(msg);
|
|
6014
7387
|
logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
|
|
@@ -6038,50 +7411,88 @@ var DaemonCore = class {
|
|
|
6038
7411
|
kind: "consumer",
|
|
6039
7412
|
attrs: {
|
|
6040
7413
|
agentId: msg.agentId,
|
|
7414
|
+
deliveryId: msg.deliveryId,
|
|
7415
|
+
delivery_correlation_id: msg.deliveryId ?? msg.message.message_id,
|
|
7416
|
+
messageId: msg.message.message_id,
|
|
7417
|
+
message_id_present: Boolean(msg.message.message_id),
|
|
6041
7418
|
seq: msg.seq
|
|
6042
7419
|
}
|
|
6043
7420
|
});
|
|
6044
7421
|
logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
|
|
6045
7422
|
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");
|
|
7423
|
+
span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
|
|
7424
|
+
const accepted = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
|
|
7425
|
+
span.addEvent("daemon.deliver_to_agent_manager", { accepted });
|
|
7426
|
+
if (!accepted) {
|
|
7427
|
+
span.end("ok", { attrs: { outcome: "not-accepted" } });
|
|
7428
|
+
break;
|
|
7429
|
+
}
|
|
6049
7430
|
const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
|
|
6050
7431
|
span.addEvent("daemon.ack.sent", { seq: ackSeq });
|
|
6051
7432
|
this.connection.send({
|
|
6052
7433
|
type: "agent:deliver:ack",
|
|
6053
7434
|
agentId: msg.agentId,
|
|
6054
7435
|
seq: ackSeq,
|
|
6055
|
-
traceparent: formatTraceparent(span.context)
|
|
7436
|
+
traceparent: formatTraceparent(span.context),
|
|
7437
|
+
deliveryId: msg.deliveryId
|
|
6056
7438
|
});
|
|
6057
|
-
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq } });
|
|
7439
|
+
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
|
|
6058
7440
|
} catch (err) {
|
|
6059
|
-
span.end("error", { attrs: {
|
|
7441
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
6060
7442
|
throw err;
|
|
6061
7443
|
}
|
|
6062
7444
|
break;
|
|
6063
7445
|
}
|
|
6064
|
-
case "agent:runtime_profile:migration":
|
|
7446
|
+
case "agent:runtime_profile:migration": {
|
|
7447
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.received", {
|
|
7448
|
+
parent: parseTraceparent(msg.traceparent),
|
|
7449
|
+
surface: "daemon",
|
|
7450
|
+
kind: "consumer",
|
|
7451
|
+
attrs: {
|
|
7452
|
+
agentId: msg.agentId,
|
|
7453
|
+
control_kind: "migration",
|
|
7454
|
+
key_present: Boolean(msg.migrationKey),
|
|
7455
|
+
launchId: msg.launchId || void 0
|
|
7456
|
+
}
|
|
7457
|
+
});
|
|
6065
7458
|
logger.info(`[Agent ${msg.agentId}] Runtime profile migration received (${msg.migrationKey})`);
|
|
6066
|
-
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message);
|
|
7459
|
+
const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context));
|
|
7460
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
6067
7461
|
break;
|
|
6068
|
-
|
|
7462
|
+
}
|
|
7463
|
+
case "agent:runtime_profile:daemon_release_notice": {
|
|
7464
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.control.received", {
|
|
7465
|
+
parent: parseTraceparent(msg.traceparent),
|
|
7466
|
+
surface: "daemon",
|
|
7467
|
+
kind: "consumer",
|
|
7468
|
+
attrs: {
|
|
7469
|
+
agentId: msg.agentId,
|
|
7470
|
+
control_kind: "daemon_release_notice",
|
|
7471
|
+
key_present: Boolean(msg.noticeKey),
|
|
7472
|
+
launchId: msg.launchId || void 0
|
|
7473
|
+
}
|
|
7474
|
+
});
|
|
6069
7475
|
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);
|
|
7476
|
+
const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context));
|
|
7477
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
6071
7478
|
break;
|
|
7479
|
+
}
|
|
6072
7480
|
case "agent:workspace:list":
|
|
6073
7481
|
this.agentManager.getFileTree(msg.agentId, msg.dirPath).then((files) => {
|
|
6074
7482
|
this.connection.send({ type: "agent:workspace:file_tree", agentId: msg.agentId, files, dirPath: msg.dirPath });
|
|
6075
7483
|
});
|
|
6076
7484
|
break;
|
|
6077
7485
|
case "agent:workspace:read":
|
|
6078
|
-
this.agentManager.readFile(msg.agentId, msg.path).then(({ content, binary }) => {
|
|
7486
|
+
this.agentManager.readFile(msg.agentId, msg.path).then(({ content, binary, size, mimeType, encoding }) => {
|
|
6079
7487
|
this.connection.send({
|
|
6080
7488
|
type: "agent:workspace:file_content",
|
|
6081
7489
|
agentId: msg.agentId,
|
|
6082
7490
|
requestId: msg.requestId,
|
|
6083
7491
|
content,
|
|
6084
|
-
binary
|
|
7492
|
+
binary,
|
|
7493
|
+
size,
|
|
7494
|
+
mimeType,
|
|
7495
|
+
encoding
|
|
6085
7496
|
});
|
|
6086
7497
|
}).catch(() => {
|
|
6087
7498
|
this.connection.send({
|
|
@@ -6089,7 +7500,8 @@ var DaemonCore = class {
|
|
|
6089
7500
|
agentId: msg.agentId,
|
|
6090
7501
|
requestId: msg.requestId,
|
|
6091
7502
|
content: null,
|
|
6092
|
-
binary: false
|
|
7503
|
+
binary: false,
|
|
7504
|
+
size: 0
|
|
6093
7505
|
});
|
|
6094
7506
|
});
|
|
6095
7507
|
break;
|
|
@@ -6165,35 +7577,59 @@ var DaemonCore = class {
|
|
|
6165
7577
|
const { ids: runtimes, versions: runtimeVersions } = this.runtimeDetector();
|
|
6166
7578
|
const runtimeInfo = runtimes.map((id) => runtimeVersions[id] ? `${id} (${runtimeVersions[id]})` : id);
|
|
6167
7579
|
logger.info(`[Daemon] Detected runtimes: ${runtimeInfo.join(", ") || "none"}`);
|
|
7580
|
+
const runningAgentIds = this.agentManager.getRunningAgentIds();
|
|
7581
|
+
const idleAgentSessions = this.agentManager.getIdleAgentSessionIds();
|
|
7582
|
+
const runtimeProfileReports = this.agentManager.getAgentRuntimeProfileReports();
|
|
6168
7583
|
this.connection.send({
|
|
6169
7584
|
type: "ready",
|
|
6170
7585
|
capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
|
|
6171
7586
|
runtimes,
|
|
6172
|
-
runningAgents:
|
|
6173
|
-
hostname: this.options.hostname ??
|
|
6174
|
-
os: this.options.osDescription ?? `${
|
|
7587
|
+
runningAgents: runningAgentIds,
|
|
7588
|
+
hostname: this.options.hostname ?? os7.hostname(),
|
|
7589
|
+
os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
|
|
6175
7590
|
daemonVersion: this.daemonVersion
|
|
6176
7591
|
});
|
|
6177
|
-
|
|
7592
|
+
this.recordDaemonTrace("daemon.ready.sent", {
|
|
7593
|
+
runtimes_count: runtimes.length,
|
|
7594
|
+
running_agents_count: runningAgentIds.length,
|
|
7595
|
+
idle_agents_count: idleAgentSessions.length,
|
|
7596
|
+
runtime_profile_reports_count: runtimeProfileReports.length,
|
|
7597
|
+
daemon_version_present: Boolean(this.daemonVersion)
|
|
7598
|
+
});
|
|
7599
|
+
for (const agentId of runningAgentIds) {
|
|
6178
7600
|
const sessionId = this.agentManager.getAgentSessionId(agentId);
|
|
6179
7601
|
const launchId = this.agentManager.getAgentLaunchId(agentId);
|
|
6180
7602
|
if (sessionId) {
|
|
6181
7603
|
this.connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
|
|
6182
7604
|
}
|
|
6183
7605
|
}
|
|
6184
|
-
for (const { agentId, sessionId, launchId } of
|
|
7606
|
+
for (const { agentId, sessionId, launchId } of idleAgentSessions) {
|
|
6185
7607
|
this.connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
|
|
6186
7608
|
}
|
|
6187
|
-
for (const report of
|
|
7609
|
+
for (const report of runtimeProfileReports) {
|
|
7610
|
+
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
7611
|
+
surface: "daemon",
|
|
7612
|
+
kind: "producer",
|
|
7613
|
+
attrs: {
|
|
7614
|
+
agentId: report.agentId,
|
|
7615
|
+
launchId: report.launchId || void 0,
|
|
7616
|
+
runtime: report.facts.runtime,
|
|
7617
|
+
model_present: Boolean(report.facts.model),
|
|
7618
|
+
session_ref_present: Boolean(report.facts.sessionRef),
|
|
7619
|
+
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
7620
|
+
}
|
|
7621
|
+
});
|
|
6188
7622
|
this.connection.send({
|
|
6189
7623
|
type: "agent:runtime_profile",
|
|
6190
7624
|
agentId: report.agentId,
|
|
6191
7625
|
facts: report.facts,
|
|
6192
|
-
launchId: report.launchId || void 0
|
|
7626
|
+
launchId: report.launchId || void 0,
|
|
7627
|
+
traceparent: formatTraceparent(span.context)
|
|
6193
7628
|
});
|
|
7629
|
+
span.end("ok");
|
|
6194
7630
|
}
|
|
6195
|
-
const agentsForSnapshot = new Set(
|
|
6196
|
-
for (const { agentId } of
|
|
7631
|
+
const agentsForSnapshot = new Set(runningAgentIds);
|
|
7632
|
+
for (const { agentId } of idleAgentSessions) {
|
|
6197
7633
|
agentsForSnapshot.add(agentId);
|
|
6198
7634
|
}
|
|
6199
7635
|
for (const agentId of agentsForSnapshot) {
|
|
@@ -6203,6 +7639,10 @@ var DaemonCore = class {
|
|
|
6203
7639
|
}
|
|
6204
7640
|
handleDisconnect() {
|
|
6205
7641
|
logger.warn("[Daemon] Lost connection \u2014 agents continue running locally");
|
|
7642
|
+
this.recordDaemonTrace("daemon.connection.local_disconnect_observed", {
|
|
7643
|
+
running_agents_count: this.agentManager.getRunningAgentIds().length,
|
|
7644
|
+
idle_agents_count: this.agentManager.getIdleAgentSessionIds().length
|
|
7645
|
+
}, "cancelled");
|
|
6206
7646
|
this.options.lifecycleHooks?.onDisconnect?.();
|
|
6207
7647
|
}
|
|
6208
7648
|
};
|