@alook/daemon 0.1.11 → 0.1.13
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/README.md +36 -0
- package/dist/cli/index.js +812 -238
- package/dist/index.js +1186 -772
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -98,7 +98,7 @@ var init_nanoid = () => {};
|
|
|
98
98
|
// src/cli/index.ts
|
|
99
99
|
import { Command, CommanderError } from "commander";
|
|
100
100
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
101
|
-
import { randomUUID as
|
|
101
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
102
102
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
103
103
|
|
|
104
104
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
@@ -14945,6 +14945,9 @@ class Name {
|
|
|
14945
14945
|
return new SQL([this]);
|
|
14946
14946
|
}
|
|
14947
14947
|
}
|
|
14948
|
+
function isDriverValueEncoder(value) {
|
|
14949
|
+
return typeof value === "object" && value !== null && "mapToDriverValue" in value && typeof value.mapToDriverValue === "function";
|
|
14950
|
+
}
|
|
14948
14951
|
var noopDecoder = {
|
|
14949
14952
|
mapFromDriverValue: (value) => value
|
|
14950
14953
|
};
|
|
@@ -15838,6 +15841,39 @@ class PrimaryKey {
|
|
|
15838
15841
|
}
|
|
15839
15842
|
}
|
|
15840
15843
|
|
|
15844
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260702.1_@opentelemetry+api@1.9.1_@typ_5aa9492b131e5fac58fa98e88e5c0ad3/node_modules/drizzle-orm/sql/expressions/conditions.js
|
|
15845
|
+
function bindIfParam(value, column) {
|
|
15846
|
+
if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
|
|
15847
|
+
return new Param(value, column);
|
|
15848
|
+
}
|
|
15849
|
+
return value;
|
|
15850
|
+
}
|
|
15851
|
+
var eq = (left, right) => {
|
|
15852
|
+
return sql`${left} = ${bindIfParam(right, left)}`;
|
|
15853
|
+
};
|
|
15854
|
+
function and(...unfilteredConditions) {
|
|
15855
|
+
const conditions = unfilteredConditions.filter((c) => c !== undefined);
|
|
15856
|
+
if (conditions.length === 0) {
|
|
15857
|
+
return;
|
|
15858
|
+
}
|
|
15859
|
+
if (conditions.length === 1) {
|
|
15860
|
+
return new SQL(conditions);
|
|
15861
|
+
}
|
|
15862
|
+
return new SQL([
|
|
15863
|
+
new StringChunk("("),
|
|
15864
|
+
sql.join(conditions, new StringChunk(" and ")),
|
|
15865
|
+
new StringChunk(")")
|
|
15866
|
+
]);
|
|
15867
|
+
}
|
|
15868
|
+
function isNotNull(value) {
|
|
15869
|
+
return sql`${value} is not null`;
|
|
15870
|
+
}
|
|
15871
|
+
|
|
15872
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260702.1_@opentelemetry+api@1.9.1_@typ_5aa9492b131e5fac58fa98e88e5c0ad3/node_modules/drizzle-orm/sql/expressions/select.js
|
|
15873
|
+
function desc(column) {
|
|
15874
|
+
return sql`${column} desc`;
|
|
15875
|
+
}
|
|
15876
|
+
|
|
15841
15877
|
// ../shared/src/db/community-machine-schema.ts
|
|
15842
15878
|
init_nanoid();
|
|
15843
15879
|
|
|
@@ -16706,6 +16742,7 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
|
|
|
16706
16742
|
|
|
16707
16743
|
// ../shared/src/community-cli-contract.ts
|
|
16708
16744
|
var DM_SERVER = ".dm";
|
|
16745
|
+
var CONTROL_HEARTBEAT_CAPABILITY = "control-heartbeat-v1";
|
|
16709
16746
|
function parseRef(ref) {
|
|
16710
16747
|
if (!ref.startsWith("/"))
|
|
16711
16748
|
throw new Error(`ref must start with "/": ${ref}`);
|
|
@@ -16781,6 +16818,10 @@ function parseSeq(s) {
|
|
|
16781
16818
|
return n;
|
|
16782
16819
|
}
|
|
16783
16820
|
var HostCommandSchema = exports_external.discriminatedUnion("type", [
|
|
16821
|
+
exports_external.strictObject({
|
|
16822
|
+
type: exports_external.literal("machine:heartbeat"),
|
|
16823
|
+
nonce: exports_external.string().min(1).max(128)
|
|
16824
|
+
}),
|
|
16784
16825
|
exports_external.object({
|
|
16785
16826
|
type: exports_external.literal("agent:wake"),
|
|
16786
16827
|
agentId: exports_external.string().min(1),
|
|
@@ -17420,6 +17461,7 @@ var CommunityMachineSummarySchema = exports_external.object({
|
|
|
17420
17461
|
var HostReadyMessageSchema = exports_external.object({
|
|
17421
17462
|
type: exports_external.literal("ready"),
|
|
17422
17463
|
runtimeReport: CommunityMachineRuntimeListSchema,
|
|
17464
|
+
capabilities: exports_external.array(exports_external.string().min(1).max(64)).max(16).optional().default([]),
|
|
17423
17465
|
runningAgents: exports_external.array(exports_external.string()).default([]),
|
|
17424
17466
|
hostname: exports_external.string().optional(),
|
|
17425
17467
|
platform: exports_external.string().optional(),
|
|
@@ -17471,6 +17513,14 @@ var AgentWakeAckMessageSchema = exports_external.object({
|
|
|
17471
17513
|
status: exports_external.enum(["ok", "error"]),
|
|
17472
17514
|
error: exports_external.object({ code: exports_external.string().optional(), message: exports_external.string().optional() }).optional()
|
|
17473
17515
|
});
|
|
17516
|
+
var MachineHeartbeatAckMessageSchema = exports_external.strictObject({
|
|
17517
|
+
type: exports_external.literal("machine_heartbeat_ack"),
|
|
17518
|
+
nonce: exports_external.string().min(1).max(128)
|
|
17519
|
+
});
|
|
17520
|
+
var DiagnosticCommandAckMessageSchema = exports_external.strictObject({
|
|
17521
|
+
type: exports_external.literal("diagnostics_ack"),
|
|
17522
|
+
reportId: DiagnosticReportIdSchema
|
|
17523
|
+
});
|
|
17474
17524
|
var CommunityPairTokenResponseSchema = exports_external.object({
|
|
17475
17525
|
tokenId: exports_external.string(),
|
|
17476
17526
|
expiresAt: exports_external.string()
|
|
@@ -17479,6 +17529,7 @@ var CommunityDaemonActivateRequestSchema = exports_external.object({
|
|
|
17479
17529
|
hostname: exports_external.string(),
|
|
17480
17530
|
platform: exports_external.string(),
|
|
17481
17531
|
arch: exports_external.string(),
|
|
17532
|
+
expectedMachineId: exports_external.string().min(1).optional(),
|
|
17482
17533
|
osRelease: exports_external.string().optional(),
|
|
17483
17534
|
daemonVersion: exports_external.string().optional(),
|
|
17484
17535
|
runtimeReport: CommunityMachineRuntimeListSchema.optional()
|
|
@@ -17486,7 +17537,12 @@ var CommunityDaemonActivateRequestSchema = exports_external.object({
|
|
|
17486
17537
|
var CommunityDaemonActivateResponseSchema = exports_external.object({
|
|
17487
17538
|
credential: exports_external.string(),
|
|
17488
17539
|
machineId: exports_external.string(),
|
|
17489
|
-
expiresAt: exports_external.string().nullable()
|
|
17540
|
+
expiresAt: exports_external.string().nullable(),
|
|
17541
|
+
sessionOutcome: exports_external.literal("committed")
|
|
17542
|
+
});
|
|
17543
|
+
var CommunityDaemonActivateErrorResponseSchema = exports_external.object({
|
|
17544
|
+
error: exports_external.string(),
|
|
17545
|
+
sessionOutcome: exports_external.enum(["not_committed", "unknown"])
|
|
17490
17546
|
});
|
|
17491
17547
|
var CommunityDaemonEnrollAgentRequestSchema = exports_external.object({
|
|
17492
17548
|
agentId: exports_external.string().min(1).max(128)
|
|
@@ -17576,13 +17632,6 @@ var CommunityAgentResolveRequestSchema = exports_external.object({
|
|
|
17576
17632
|
var CommunityAgentListChannelsRequestSchema = exports_external.object({
|
|
17577
17633
|
server: exports_external.string().min(1).optional()
|
|
17578
17634
|
});
|
|
17579
|
-
var CommunityAgentCreatePostRequestSchema = exports_external.object({
|
|
17580
|
-
forum: exports_external.string().min(1),
|
|
17581
|
-
title: exports_external.string().min(1),
|
|
17582
|
-
content: CommunityAgentMessageContentSchema,
|
|
17583
|
-
attachments: exports_external.array(exports_external.string().min(1)).max(MAX_ATTACHMENTS_PER_MESSAGE).default([]),
|
|
17584
|
-
nonce: exports_external.string().min(1).max(128).optional()
|
|
17585
|
-
}).refine((d) => d.content.text.trim().length > 0 || d.attachments.length > 0, { message: "post must have text or attachments" });
|
|
17586
17635
|
var CommunityAgentListMembersRequestSchema = exports_external.object({
|
|
17587
17636
|
server: exports_external.string().min(1),
|
|
17588
17637
|
limit: exports_external.number().int().positive().optional(),
|
|
@@ -17596,7 +17645,9 @@ var CommunityAgentJoinServerRequestSchema = exports_external.object({
|
|
|
17596
17645
|
invite: exports_external.string().min(1)
|
|
17597
17646
|
});
|
|
17598
17647
|
var CommunityAgentNapRequestSchema = exports_external.object({
|
|
17599
|
-
handoff: exports_external.string().
|
|
17648
|
+
handoff: exports_external.string().refine((value) => value.trim().length > 0, {
|
|
17649
|
+
message: "handoff is required"
|
|
17650
|
+
})
|
|
17600
17651
|
});
|
|
17601
17652
|
var CommunityAgentReactAddRequestSchema = exports_external.object({
|
|
17602
17653
|
channel: exports_external.string().min(1),
|
|
@@ -17748,6 +17799,7 @@ var communityChannel = sqliteTable("community_channel", {
|
|
|
17748
17799
|
index("idx_channel_server_position").on(t.serverId, t.position),
|
|
17749
17800
|
index("idx_channel_server_last_message").on(t.serverId, t.lastMessageAt),
|
|
17750
17801
|
index("idx_channel_parent").on(t.parentChannelId),
|
|
17802
|
+
index("idx_channel_forum_created").on(t.parentChannelId, desc(t.createdAt), desc(t.id)).where(and(eq(t.type, "thread"), eq(t.archived, 0), isNotNull(t.parentMessageId))),
|
|
17751
17803
|
uniqueIndex("idx_channel_server_name").on(t.serverId, t.name).where(sql`parent_channel_id IS NULL`)
|
|
17752
17804
|
]);
|
|
17753
17805
|
var communityChannelMember = sqliteTable("community_channel_member", {
|
|
@@ -18130,26 +18182,6 @@ var channelJoinBaselineGuard = sql`${communityMessage.createdAt} > COALESCE(${co
|
|
|
18130
18182
|
|
|
18131
18183
|
// ../shared/src/db/queries/community/mention.ts
|
|
18132
18184
|
var MENTION_INSERT_MAX_ROWS = maxRowsPerInsert(5);
|
|
18133
|
-
// ../shared/src/community/bot-activity-presets.ts
|
|
18134
|
-
var BOT_ACTIVITY_PRESETS = {
|
|
18135
|
-
idle: { emoji: "\uD83D\uDCA4", text: "Idle" },
|
|
18136
|
-
starting: { emoji: "\uD83C\uDF00", text: "Waking up" },
|
|
18137
|
-
stopping: { emoji: "\uD83C\uDF19", text: "Wrapping up" }
|
|
18138
|
-
};
|
|
18139
|
-
var RUNNING_PRESETS = [
|
|
18140
|
-
{ emoji: "⚡", text: "Working on it" },
|
|
18141
|
-
{ emoji: "\uD83D\uDEE0️", text: "Cooking" },
|
|
18142
|
-
{ emoji: "\uD83E\uDDE0", text: "Thinking hard" },
|
|
18143
|
-
{ emoji: "\uD83D\uDD27", text: "Tinkering" },
|
|
18144
|
-
{ emoji: "\uD83D\uDE80", text: "On it" },
|
|
18145
|
-
{ emoji: "\uD83D\uDD25", text: "In the zone" }
|
|
18146
|
-
];
|
|
18147
|
-
var BOT_ACTIVITY_STATUS_PAIRS = [
|
|
18148
|
-
BOT_ACTIVITY_PRESETS.idle,
|
|
18149
|
-
BOT_ACTIVITY_PRESETS.starting,
|
|
18150
|
-
BOT_ACTIVITY_PRESETS.stopping,
|
|
18151
|
-
...RUNNING_PRESETS
|
|
18152
|
-
];
|
|
18153
18185
|
// ../shared/src/community-ws-events.ts
|
|
18154
18186
|
var string4 = exports_external.string();
|
|
18155
18187
|
var nullableString = string4.nullable();
|
|
@@ -18606,6 +18638,68 @@ var WS_EVENTS = {
|
|
|
18606
18638
|
BOT_AUDIT_EVENT: "community:bot.audit_event"
|
|
18607
18639
|
};
|
|
18608
18640
|
var COMMUNITY_EVENT_TYPES = new Set(Object.values(WS_EVENTS));
|
|
18641
|
+
var COMMUNITY_USER_TARGET_MAX_BYTES = 128;
|
|
18642
|
+
function utf8ByteLength(value) {
|
|
18643
|
+
return new TextEncoder().encode(value).byteLength;
|
|
18644
|
+
}
|
|
18645
|
+
function isWellFormedUnicode(value) {
|
|
18646
|
+
for (let index2 = 0;index2 < value.length; index2 += 1) {
|
|
18647
|
+
const codeUnit = value.charCodeAt(index2);
|
|
18648
|
+
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
18649
|
+
const next = value.charCodeAt(index2 + 1);
|
|
18650
|
+
if (!Number.isInteger(next) || next < 56320 || next > 57343)
|
|
18651
|
+
return false;
|
|
18652
|
+
index2 += 1;
|
|
18653
|
+
} else if (codeUnit >= 56320 && codeUnit <= 57343) {
|
|
18654
|
+
return false;
|
|
18655
|
+
}
|
|
18656
|
+
}
|
|
18657
|
+
return true;
|
|
18658
|
+
}
|
|
18659
|
+
function isValidCommunityUserTarget(value) {
|
|
18660
|
+
return typeof value === "string" && value.length > 0 && isWellFormedUnicode(value) && utf8ByteLength(value) <= COMMUNITY_USER_TARGET_MAX_BYTES;
|
|
18661
|
+
}
|
|
18662
|
+
|
|
18663
|
+
// ../shared/src/community-message-delivery.ts
|
|
18664
|
+
var MESSAGE_DELIVERY_MAX_USERS = 1000;
|
|
18665
|
+
var target = exports_external.string().refine(isValidCommunityUserTarget);
|
|
18666
|
+
var targetList = exports_external.array(target).max(MESSAGE_DELIVERY_MAX_USERS);
|
|
18667
|
+
var memberAddedSchema = exports_external.strictObject({
|
|
18668
|
+
userId: target,
|
|
18669
|
+
serverId: exports_external.string().min(1),
|
|
18670
|
+
channelId: exports_external.string().min(1)
|
|
18671
|
+
});
|
|
18672
|
+
var batchShape = exports_external.strictObject({
|
|
18673
|
+
messageId: exports_external.string().min(1),
|
|
18674
|
+
messageEvent: exports_external.unknown(),
|
|
18675
|
+
contentUserIds: targetList,
|
|
18676
|
+
unreadPlainUserIds: targetList,
|
|
18677
|
+
unreadMentionUserIds: targetList,
|
|
18678
|
+
mentionUserIds: targetList,
|
|
18679
|
+
memberAdded: memberAddedSchema.optional(),
|
|
18680
|
+
parentProjection: exports_external.unknown().optional(),
|
|
18681
|
+
parentProjectionUserIds: targetList.optional()
|
|
18682
|
+
});
|
|
18683
|
+
// ../shared/src/community/bot-activity-presets.ts
|
|
18684
|
+
var BOT_ACTIVITY_PRESETS = {
|
|
18685
|
+
idle: { emoji: "\uD83D\uDCA4", text: "Idle" },
|
|
18686
|
+
starting: { emoji: "\uD83C\uDF00", text: "Waking up" },
|
|
18687
|
+
stopping: { emoji: "\uD83C\uDF19", text: "Wrapping up" }
|
|
18688
|
+
};
|
|
18689
|
+
var RUNNING_PRESETS = [
|
|
18690
|
+
{ emoji: "⚡", text: "Working on it" },
|
|
18691
|
+
{ emoji: "\uD83D\uDEE0️", text: "Cooking" },
|
|
18692
|
+
{ emoji: "\uD83E\uDDE0", text: "Thinking hard" },
|
|
18693
|
+
{ emoji: "\uD83D\uDD27", text: "Tinkering" },
|
|
18694
|
+
{ emoji: "\uD83D\uDE80", text: "On it" },
|
|
18695
|
+
{ emoji: "\uD83D\uDD25", text: "In the zone" }
|
|
18696
|
+
];
|
|
18697
|
+
var BOT_ACTIVITY_STATUS_PAIRS = [
|
|
18698
|
+
BOT_ACTIVITY_PRESETS.idle,
|
|
18699
|
+
BOT_ACTIVITY_PRESETS.starting,
|
|
18700
|
+
BOT_ACTIVITY_PRESETS.stopping,
|
|
18701
|
+
...RUNNING_PRESETS
|
|
18702
|
+
];
|
|
18609
18703
|
// ../shared/src/db/index.ts
|
|
18610
18704
|
var allSchema = { ...exports_schema, ...exports_community_schema, ...exports_community_machine_schema };
|
|
18611
18705
|
// ../shared/src/db/queries/task.ts
|
|
@@ -18769,50 +18863,6 @@ function createProxyServerApi(config2) {
|
|
|
18769
18863
|
});
|
|
18770
18864
|
return parseJsonResponse(res, "send");
|
|
18771
18865
|
}
|
|
18772
|
-
async function callCreatePost(req) {
|
|
18773
|
-
const endpoint = `${base}/api/community/channels/${REF_PLACEHOLDER_ID}/messages`;
|
|
18774
|
-
const openerNonce = req.nonce !== undefined ? `${req.nonce}:opener` : undefined;
|
|
18775
|
-
const replyNonce = req.nonce !== undefined ? `${req.nonce}:reply` : undefined;
|
|
18776
|
-
const openerRes = await fetchImpl(endpoint, {
|
|
18777
|
-
method: "POST",
|
|
18778
|
-
headers: {
|
|
18779
|
-
"content-type": "application/json",
|
|
18780
|
-
authorization: `Bearer ${config2.voucher}`
|
|
18781
|
-
},
|
|
18782
|
-
body: JSON.stringify({
|
|
18783
|
-
channel: req.forum,
|
|
18784
|
-
content: { text: req.title },
|
|
18785
|
-
attachments: req.attachments ?? [],
|
|
18786
|
-
...openerNonce !== undefined ? { nonce: openerNonce } : {}
|
|
18787
|
-
})
|
|
18788
|
-
});
|
|
18789
|
-
const opener = await parseJsonResponse(openerRes, "createPost opener");
|
|
18790
|
-
if (opener.state !== "sent" || !opener.message || !opener.threadId) {
|
|
18791
|
-
throw new Error("createPost: upstream response missing opener thread");
|
|
18792
|
-
}
|
|
18793
|
-
const threadRef = `${req.forum}/#${opener.message.seq.replace(/^#/, "")}`;
|
|
18794
|
-
const replyRes = await fetchImpl(endpoint, {
|
|
18795
|
-
method: "POST",
|
|
18796
|
-
headers: {
|
|
18797
|
-
"content-type": "application/json",
|
|
18798
|
-
authorization: `Bearer ${config2.voucher}`
|
|
18799
|
-
},
|
|
18800
|
-
body: JSON.stringify({
|
|
18801
|
-
channel: threadRef,
|
|
18802
|
-
content: req.content,
|
|
18803
|
-
attachments: req.attachments ?? [],
|
|
18804
|
-
...replyNonce !== undefined ? { nonce: replyNonce } : {}
|
|
18805
|
-
})
|
|
18806
|
-
});
|
|
18807
|
-
const reply = await parseJsonResponse(replyRes, "createPost reply");
|
|
18808
|
-
if (reply.state !== "sent" || !reply.message)
|
|
18809
|
-
throw new Error("createPost: upstream response missing reply");
|
|
18810
|
-
return {
|
|
18811
|
-
ref: reply.message.channel,
|
|
18812
|
-
name: req.title,
|
|
18813
|
-
seq: Number(reply.message.seq.replace(/^#/, ""))
|
|
18814
|
-
};
|
|
18815
|
-
}
|
|
18816
18866
|
async function callRead(req) {
|
|
18817
18867
|
const q = new URLSearchParams;
|
|
18818
18868
|
q.set("ref", req.channel);
|
|
@@ -19096,7 +19146,6 @@ function createProxyServerApi(config2) {
|
|
|
19096
19146
|
inboxSnapshot: (_r) => callInboxSnapshot(),
|
|
19097
19147
|
ack: callAck,
|
|
19098
19148
|
send: callSend,
|
|
19099
|
-
createPost: callCreatePost,
|
|
19100
19149
|
read: callRead,
|
|
19101
19150
|
resolve: callResolve,
|
|
19102
19151
|
listMembers: callListMembers,
|
|
@@ -19162,7 +19211,7 @@ function cliCommandsSection() {
|
|
|
19162
19211
|
"### Messaging",
|
|
19163
19212
|
"",
|
|
19164
19213
|
`1. \`${CLI} inbox pull\` — fetch unread messages (advances your read waterline by default, ` + `so they won't re-pull; \`--no-ack\` to peek without advancing).`,
|
|
19165
|
-
`2. \`${CLI} message send\` — send to a channel, DM, or thread.
|
|
19214
|
+
`2. \`${CLI} message send\` — send to a channel, DM, or thread. ` + `For a short body, use explicit \`--stdin\` with a quoted heredoc; for a long or complicated body, ` + `use \`--file <path>\`. Attach with \`--attachment <id>\` (repeatable, order matters). Optionally add ` + `\`--remind-after <duration>\` to be reminded if the same channel, thread, or DM receives no ` + `newer message after your send. Use a whole number of minutes or hours from \`1m\` to \`24h\`, ` + `such as \`15m\` or \`2h\`.`,
|
|
19166
19215
|
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted. Feed it into ` + `\`message send --attachment <id>\`.`,
|
|
19167
19216
|
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
19168
19217
|
`5. \`${CLI} message emoji --target <ref> --emoji <e>\` — react with a single emoji. ` + `Works on channel messages (\`/<server>/<channel>#N\`), DM messages ` + `(\`/.dm/<peer>#N\`), and thread-reply messages (\`/<server>/<channel>/#N#M\`).`,
|
|
@@ -19193,7 +19242,7 @@ function cliCommandsSection() {
|
|
|
19193
19242
|
"",
|
|
19194
19243
|
"### Context Lifecycle",
|
|
19195
19244
|
"",
|
|
19196
|
-
`1. \`${CLI} nap --handoff <file>\`
|
|
19245
|
+
`1. \`${CLI} nap --handoff <file>\` — reset your current session and ` + `start fresh. The required handoff is injected into the new session so your future self can ` + `quickly pick up unfinished work. Never nap on your own; only do it when someone explicitly asks.`,
|
|
19197
19246
|
"",
|
|
19198
19247
|
"### Output format",
|
|
19199
19248
|
"",
|
|
@@ -19212,9 +19261,11 @@ function messagingSection() {
|
|
|
19212
19261
|
"You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying. Use the same `message send` command whether you're replying or " + "starting a conversation.",
|
|
19213
19262
|
"",
|
|
19214
19263
|
"- Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, read history (below) or DM the relevant people.",
|
|
19215
|
-
|
|
19216
|
-
|
|
19217
|
-
`-
|
|
19264
|
+
"Free-form message bodies never go in command arguments.",
|
|
19265
|
+
"",
|
|
19266
|
+
`- Short reply: use \`${CLI} message send --target <ref> --stdin\` with the quoted-heredoc form shown ` + "under *Message formatting*. Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
|
|
19267
|
+
`- Long or complicated: write the body to a temporary file with a filesystem tool, then ` + `\`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
|
|
19268
|
+
`- Cite a specific message: add \`--reply "#37"\` to either form — \`--reply\` takes the \`#N\` seq ` + "(within `--target`) of the message you're answering.",
|
|
19218
19269
|
"",
|
|
19219
19270
|
"### Context refs",
|
|
19220
19271
|
"",
|
|
@@ -19254,8 +19305,10 @@ function messagingSection() {
|
|
|
19254
19305
|
"- **Mentions** — `@name#NNNN` calls that person's attention specifically. In a private " + "channel, first verify they " + `are a member with \`${CLI} channel member --channel <ref>\` before mentioning them.`,
|
|
19255
19306
|
"",
|
|
19256
19307
|
"```bash",
|
|
19257
|
-
"#
|
|
19258
|
-
`${CLI} message send --target "/demo#1234/general" --
|
|
19308
|
+
"# Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
|
|
19309
|
+
`${CLI} message send --target "/demo#1234/general" --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
19310
|
+
"@alice#0001 Please review /demo#1234/general#42",
|
|
19311
|
+
"ALOOK_MESSAGE_7F3C",
|
|
19259
19312
|
"```",
|
|
19260
19313
|
"",
|
|
19261
19314
|
"### Pulled messages",
|
|
@@ -19321,7 +19374,7 @@ function utilsSection() {
|
|
|
19321
19374
|
"",
|
|
19322
19375
|
`Use \`message send --remind-after <duration>\` when you send something that may need a later ` + "follow-up — for example, a question, approval request, handoff, or blocker — and silence would " + "leave the work unfinished. If no newer message appears in that channel, thread, or DM during " + "the duration, you'll receive a reminder to return and decide what to do next. Don't add it to " + "ordinary messages that need no follow-up.",
|
|
19323
19376
|
"",
|
|
19324
|
-
`Example:
|
|
19377
|
+
`Example: ${CLI} message send --target "/demo#1234/team" --remind-after 1m --file ./message.md`
|
|
19325
19378
|
].join(`
|
|
19326
19379
|
`);
|
|
19327
19380
|
}
|
|
@@ -19658,8 +19711,8 @@ function ensureSymlinks(workDir) {
|
|
|
19658
19711
|
try {
|
|
19659
19712
|
const stat = lstatSync(aliasPath);
|
|
19660
19713
|
if (stat.isSymbolicLink()) {
|
|
19661
|
-
const
|
|
19662
|
-
if (
|
|
19714
|
+
const target2 = readlinkSync(aliasPath);
|
|
19715
|
+
if (target2 === CANONICAL_FILE)
|
|
19663
19716
|
continue;
|
|
19664
19717
|
unlinkSync2(aliasPath);
|
|
19665
19718
|
} else {
|
|
@@ -19798,11 +19851,11 @@ function buildClaudeProviderIsolationEnv(ctx) {
|
|
|
19798
19851
|
fs4.mkdirSync(configDir, { recursive: true });
|
|
19799
19852
|
const hostClaude = path4.join(process.env.HOME || ".", ".claude");
|
|
19800
19853
|
for (const sub of ["skills", "commands"]) {
|
|
19801
|
-
const
|
|
19854
|
+
const target2 = path4.join(hostClaude, sub);
|
|
19802
19855
|
const link = path4.join(configDir, sub);
|
|
19803
19856
|
try {
|
|
19804
|
-
if (fs4.existsSync(
|
|
19805
|
-
fs4.symlinkSync(
|
|
19857
|
+
if (fs4.existsSync(target2) && !fs4.existsSync(link))
|
|
19858
|
+
fs4.symlinkSync(target2, link);
|
|
19806
19859
|
} catch {}
|
|
19807
19860
|
}
|
|
19808
19861
|
return {
|
|
@@ -20036,9 +20089,9 @@ function resolveHomePath(relativePath, deps = {}) {
|
|
|
20036
20089
|
}
|
|
20037
20090
|
function resolveSpawnSpec(command, args, override, deps = {}, platform = process.platform) {
|
|
20038
20091
|
const trimmed = override?.trim();
|
|
20039
|
-
const
|
|
20092
|
+
const target2 = trimmed && trimmed.length > 0 ? trimmed : command;
|
|
20040
20093
|
const looksLikePath = trimmed !== undefined && trimmed.length > 0 && /[\\/]/.test(trimmed);
|
|
20041
|
-
const resolved = looksLikePath ?
|
|
20094
|
+
const resolved = looksLikePath ? target2 : resolveCommandOnPath(target2, deps) ?? target2;
|
|
20042
20095
|
return { command: resolved, args, shell: needsWindowsShimShell(resolved, platform) };
|
|
20043
20096
|
}
|
|
20044
20097
|
function resolveClaudeCommand(deps = {}) {
|
|
@@ -21375,8 +21428,8 @@ function listRuntimeIds() {
|
|
|
21375
21428
|
var SELECTABLE_RUNTIMES = new Set(["claude", "codex", "opencode", "pi", "cursor"]);
|
|
21376
21429
|
function resolveAlookCliPath(moduleDir) {
|
|
21377
21430
|
const thisDir = moduleDir ?? path8.dirname(fileURLToPath(import.meta.url));
|
|
21378
|
-
const
|
|
21379
|
-
return fs6.existsSync(
|
|
21431
|
+
const target2 = path8.basename(thisDir) === "dist" ? path8.resolve(thisDir, "cli", "index.js") : path8.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
|
|
21432
|
+
return fs6.existsSync(target2) ? target2 : null;
|
|
21380
21433
|
}
|
|
21381
21434
|
function deriveCliFallbackCandidates(cliPath) {
|
|
21382
21435
|
if (!cliPath)
|
|
@@ -21780,6 +21833,236 @@ function writeStatusFile(path9, snapshot) {
|
|
|
21780
21833
|
} catch {}
|
|
21781
21834
|
}
|
|
21782
21835
|
|
|
21836
|
+
// src/manager/wakeCoordinator.ts
|
|
21837
|
+
class WakeCoordinator {
|
|
21838
|
+
agents = new Map;
|
|
21839
|
+
async run(command, dispatch, onDesiredAdvance) {
|
|
21840
|
+
const state = this.agent(command.agentId);
|
|
21841
|
+
const channel2 = command.unreadNotice.channel;
|
|
21842
|
+
const seq = command.unreadNotice.latestSeq;
|
|
21843
|
+
const scope = state.scopes.get(channel2);
|
|
21844
|
+
const coveredSeq = Math.max(scope?.modelSeenSeq ?? 0, scope?.admittedSeq ?? 0);
|
|
21845
|
+
if (seq <= coveredSeq) {
|
|
21846
|
+
this.rememberReplacement(state, command);
|
|
21847
|
+
return { state: "suppressed", coveredSeq };
|
|
21848
|
+
}
|
|
21849
|
+
const desiredAdvanced = !scope || seq > scope.desiredSeq;
|
|
21850
|
+
if (!scope || seq >= scope.desiredSeq) {
|
|
21851
|
+
state.scopes.set(channel2, {
|
|
21852
|
+
desiredSeq: Math.max(scope?.desiredSeq ?? 0, seq),
|
|
21853
|
+
admittedSeq: scope?.admittedSeq ?? 0,
|
|
21854
|
+
modelSeenSeq: scope?.modelSeenSeq ?? 0,
|
|
21855
|
+
command
|
|
21856
|
+
});
|
|
21857
|
+
}
|
|
21858
|
+
if (desiredAdvanced)
|
|
21859
|
+
onDesiredAdvance?.(command);
|
|
21860
|
+
state.dispatch = dispatch;
|
|
21861
|
+
if (state.active || state.admitting) {
|
|
21862
|
+
this.rememberReplacement(state, command);
|
|
21863
|
+
return { state: "suppressed", coveredSeq: seq };
|
|
21864
|
+
}
|
|
21865
|
+
await this.admit(command.agentId, command);
|
|
21866
|
+
return { state: "accepted" };
|
|
21867
|
+
}
|
|
21868
|
+
modelSeenGeneration(agentId) {
|
|
21869
|
+
return this.agent(agentId).observationGeneration;
|
|
21870
|
+
}
|
|
21871
|
+
recordModelSeen(agentId, messages, generation) {
|
|
21872
|
+
const state = this.agent(agentId);
|
|
21873
|
+
if (generation !== state.observationGeneration)
|
|
21874
|
+
return false;
|
|
21875
|
+
for (const message2 of messages) {
|
|
21876
|
+
const seq = Number(message2.seq.startsWith("#") ? message2.seq.slice(1) : message2.seq);
|
|
21877
|
+
if (!Number.isSafeInteger(seq) || seq <= 0)
|
|
21878
|
+
continue;
|
|
21879
|
+
const scope = state.scopes.get(message2.channel);
|
|
21880
|
+
if (scope) {
|
|
21881
|
+
scope.modelSeenSeq = Math.max(scope.modelSeenSeq, seq);
|
|
21882
|
+
} else {
|
|
21883
|
+
state.scopes.set(message2.channel, {
|
|
21884
|
+
desiredSeq: 0,
|
|
21885
|
+
admittedSeq: 0,
|
|
21886
|
+
modelSeenSeq: seq,
|
|
21887
|
+
command: null
|
|
21888
|
+
});
|
|
21889
|
+
}
|
|
21890
|
+
}
|
|
21891
|
+
return true;
|
|
21892
|
+
}
|
|
21893
|
+
recordAgentActivity(agentId, activity) {
|
|
21894
|
+
const state = this.agent(agentId);
|
|
21895
|
+
if (activity === "starting" || activity === "running") {
|
|
21896
|
+
state.active = true;
|
|
21897
|
+
return;
|
|
21898
|
+
}
|
|
21899
|
+
if (activity !== "idle")
|
|
21900
|
+
return;
|
|
21901
|
+
state.active = false;
|
|
21902
|
+
state.activeAdmission = null;
|
|
21903
|
+
const next = this.nextPending(state);
|
|
21904
|
+
if (next && state.dispatch)
|
|
21905
|
+
this.admit(agentId, next);
|
|
21906
|
+
}
|
|
21907
|
+
recordDeliveryAck(agentId, launchId, status) {
|
|
21908
|
+
const state = this.agents.get(agentId);
|
|
21909
|
+
const admission = state?.activeAdmission;
|
|
21910
|
+
if (!state || !admission || admission.launchId !== launchId)
|
|
21911
|
+
return;
|
|
21912
|
+
if (status === "ok")
|
|
21913
|
+
return;
|
|
21914
|
+
for (const [channel2, coverage] of admission.coverage) {
|
|
21915
|
+
const scope = state.scopes.get(channel2);
|
|
21916
|
+
if (scope?.admittedSeq === coverage.admittedSeq) {
|
|
21917
|
+
scope.admittedSeq = coverage.previousSeq;
|
|
21918
|
+
}
|
|
21919
|
+
}
|
|
21920
|
+
state.active = false;
|
|
21921
|
+
state.failedAdmissionLaunchId = launchId;
|
|
21922
|
+
state.retryAfterFailure = state.coalescedReplacement;
|
|
21923
|
+
state.coalescedReplacement = null;
|
|
21924
|
+
state.activeAdmission = null;
|
|
21925
|
+
if (!state.admitting && state.retryAfterFailure) {
|
|
21926
|
+
this.retryFailedAdmission(agentId, state).catch(() => {});
|
|
21927
|
+
}
|
|
21928
|
+
}
|
|
21929
|
+
invalidate(agentId, clearModelSeen) {
|
|
21930
|
+
const state = this.agent(agentId);
|
|
21931
|
+
state.observationGeneration += 1;
|
|
21932
|
+
state.active = false;
|
|
21933
|
+
state.admitting = false;
|
|
21934
|
+
state.activeAdmission = null;
|
|
21935
|
+
state.failedAdmissionLaunchId = null;
|
|
21936
|
+
state.coalescedReplacement = null;
|
|
21937
|
+
state.retryAfterFailure = null;
|
|
21938
|
+
if (clearModelSeen) {
|
|
21939
|
+
state.scopes.clear();
|
|
21940
|
+
return;
|
|
21941
|
+
}
|
|
21942
|
+
for (const scope of state.scopes.values()) {
|
|
21943
|
+
scope.desiredSeq = scope.modelSeenSeq;
|
|
21944
|
+
scope.admittedSeq = scope.modelSeenSeq;
|
|
21945
|
+
}
|
|
21946
|
+
}
|
|
21947
|
+
agent(agentId) {
|
|
21948
|
+
let state = this.agents.get(agentId);
|
|
21949
|
+
if (!state) {
|
|
21950
|
+
state = {
|
|
21951
|
+
active: false,
|
|
21952
|
+
admitting: false,
|
|
21953
|
+
activeAdmission: null,
|
|
21954
|
+
failedAdmissionLaunchId: null,
|
|
21955
|
+
coalescedReplacement: null,
|
|
21956
|
+
retryAfterFailure: null,
|
|
21957
|
+
observationGeneration: 0,
|
|
21958
|
+
scopes: new Map,
|
|
21959
|
+
dispatch: null
|
|
21960
|
+
};
|
|
21961
|
+
this.agents.set(agentId, state);
|
|
21962
|
+
}
|
|
21963
|
+
return state;
|
|
21964
|
+
}
|
|
21965
|
+
nextPending(state) {
|
|
21966
|
+
let next = null;
|
|
21967
|
+
for (const scope of state.scopes.values()) {
|
|
21968
|
+
if (scope.desiredSeq <= Math.max(scope.modelSeenSeq, scope.admittedSeq))
|
|
21969
|
+
continue;
|
|
21970
|
+
if (!scope.command)
|
|
21971
|
+
continue;
|
|
21972
|
+
if (scope.command.launchId === state.failedAdmissionLaunchId)
|
|
21973
|
+
continue;
|
|
21974
|
+
if (!next || scope.desiredSeq > next.desiredSeq)
|
|
21975
|
+
next = scope;
|
|
21976
|
+
}
|
|
21977
|
+
return next?.command ?? null;
|
|
21978
|
+
}
|
|
21979
|
+
async admit(agentId, preferred) {
|
|
21980
|
+
const state = this.agent(agentId);
|
|
21981
|
+
if (state.active || state.admitting || !state.dispatch)
|
|
21982
|
+
return;
|
|
21983
|
+
const admissionGeneration = state.observationGeneration;
|
|
21984
|
+
const command = preferred ?? this.nextPending(state);
|
|
21985
|
+
if (!command)
|
|
21986
|
+
return;
|
|
21987
|
+
const commandScope = state.scopes.get(command.unreadNotice.channel);
|
|
21988
|
+
if (commandScope && command.unreadNotice.latestSeq >= commandScope.desiredSeq) {
|
|
21989
|
+
commandScope.command = command;
|
|
21990
|
+
}
|
|
21991
|
+
const coverage = new Map;
|
|
21992
|
+
for (const [channel2, scope] of state.scopes) {
|
|
21993
|
+
if (scope.desiredSeq <= Math.max(scope.modelSeenSeq, scope.admittedSeq))
|
|
21994
|
+
continue;
|
|
21995
|
+
coverage.set(channel2, {
|
|
21996
|
+
previousSeq: scope.admittedSeq,
|
|
21997
|
+
admittedSeq: scope.desiredSeq
|
|
21998
|
+
});
|
|
21999
|
+
scope.admittedSeq = scope.desiredSeq;
|
|
22000
|
+
scope.command = command;
|
|
22001
|
+
}
|
|
22002
|
+
if (coverage.size === 0)
|
|
22003
|
+
return;
|
|
22004
|
+
state.admitting = true;
|
|
22005
|
+
state.active = true;
|
|
22006
|
+
state.failedAdmissionLaunchId = null;
|
|
22007
|
+
state.coalescedReplacement = null;
|
|
22008
|
+
state.activeAdmission = { launchId: command.launchId, coverage };
|
|
22009
|
+
let dispatchError;
|
|
22010
|
+
try {
|
|
22011
|
+
await state.dispatch(command);
|
|
22012
|
+
} catch (error51) {
|
|
22013
|
+
if (state.observationGeneration === admissionGeneration) {
|
|
22014
|
+
for (const [channel2, admitted] of coverage) {
|
|
22015
|
+
const scope = state.scopes.get(channel2);
|
|
22016
|
+
if (scope?.admittedSeq === admitted.admittedSeq) {
|
|
22017
|
+
scope.admittedSeq = admitted.previousSeq;
|
|
22018
|
+
}
|
|
22019
|
+
}
|
|
22020
|
+
state.active = false;
|
|
22021
|
+
state.failedAdmissionLaunchId = command.launchId;
|
|
22022
|
+
state.retryAfterFailure = state.coalescedReplacement;
|
|
22023
|
+
state.coalescedReplacement = null;
|
|
22024
|
+
state.activeAdmission = null;
|
|
22025
|
+
}
|
|
22026
|
+
dispatchError = error51;
|
|
22027
|
+
} finally {
|
|
22028
|
+
if (state.observationGeneration === admissionGeneration) {
|
|
22029
|
+
state.admitting = false;
|
|
22030
|
+
}
|
|
22031
|
+
}
|
|
22032
|
+
if (state.observationGeneration === admissionGeneration) {
|
|
22033
|
+
await this.retryFailedAdmission(agentId, state);
|
|
22034
|
+
if (!state.active && !state.admitting) {
|
|
22035
|
+
const next = this.nextPending(state);
|
|
22036
|
+
if (next)
|
|
22037
|
+
await this.admit(agentId, next);
|
|
22038
|
+
}
|
|
22039
|
+
}
|
|
22040
|
+
if (dispatchError)
|
|
22041
|
+
throw dispatchError;
|
|
22042
|
+
}
|
|
22043
|
+
rememberReplacement(state, command) {
|
|
22044
|
+
const failedOrActiveLaunchId = state.activeAdmission?.launchId ?? state.failedAdmissionLaunchId;
|
|
22045
|
+
if (!failedOrActiveLaunchId || command.launchId === failedOrActiveLaunchId)
|
|
22046
|
+
return;
|
|
22047
|
+
const scope = state.scopes.get(command.unreadNotice.channel);
|
|
22048
|
+
if (!scope || command.unreadNotice.latestSeq < scope.desiredSeq)
|
|
22049
|
+
return;
|
|
22050
|
+
state.coalescedReplacement = command;
|
|
22051
|
+
if (state.failedAdmissionLaunchId)
|
|
22052
|
+
state.retryAfterFailure = command;
|
|
22053
|
+
}
|
|
22054
|
+
async retryFailedAdmission(agentId, state) {
|
|
22055
|
+
if (state.active || state.admitting)
|
|
22056
|
+
return;
|
|
22057
|
+
const replacement = state.retryAfterFailure;
|
|
22058
|
+
state.retryAfterFailure = null;
|
|
22059
|
+
if (!replacement)
|
|
22060
|
+
return;
|
|
22061
|
+
state.failedAdmissionLaunchId = null;
|
|
22062
|
+
await this.admit(agentId, replacement);
|
|
22063
|
+
}
|
|
22064
|
+
}
|
|
22065
|
+
|
|
21783
22066
|
// src/server/wsControlChannel.ts
|
|
21784
22067
|
var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
|
|
21785
22068
|
var DEFAULT_PING_INTERVAL_MS = 15000;
|
|
@@ -21794,6 +22077,7 @@ class WsControlChannel {
|
|
|
21794
22077
|
opts;
|
|
21795
22078
|
statusValue = "idle";
|
|
21796
22079
|
commandCbs = [];
|
|
22080
|
+
wakeDesiredCbs = [];
|
|
21797
22081
|
resyncHooks = [];
|
|
21798
22082
|
ws = null;
|
|
21799
22083
|
attempt = 0;
|
|
@@ -21803,6 +22087,7 @@ class WsControlChannel {
|
|
|
21803
22087
|
pongDeadline = 0;
|
|
21804
22088
|
resyncProvider = null;
|
|
21805
22089
|
log;
|
|
22090
|
+
wakeCoordinator = new WakeCoordinator;
|
|
21806
22091
|
constructor(opts) {
|
|
21807
22092
|
this.opts = opts;
|
|
21808
22093
|
this.log = opts.logger ?? createLogger2({ header: "@alook/daemon:ws" });
|
|
@@ -21825,6 +22110,30 @@ class WsControlChannel {
|
|
|
21825
22110
|
onCommand(cb) {
|
|
21826
22111
|
this.commandCbs.push(cb);
|
|
21827
22112
|
}
|
|
22113
|
+
onWakeDesiredAdvance(cb) {
|
|
22114
|
+
this.wakeDesiredCbs.push(cb);
|
|
22115
|
+
}
|
|
22116
|
+
async ingestCommand(frame) {
|
|
22117
|
+
const parsed = HostCommandSchema.safeParse(frame);
|
|
22118
|
+
if (!parsed.success) {
|
|
22119
|
+
this.log.warn("dropped malformed HostCommand frame", {
|
|
22120
|
+
type: typeof frame === "object" && frame !== null && "type" in frame ? String(frame.type) : "unknown",
|
|
22121
|
+
issues: parsed.error.issues.map((issue3) => ({
|
|
22122
|
+
path: issue3.path.join("."),
|
|
22123
|
+
code: issue3.code
|
|
22124
|
+
}))
|
|
22125
|
+
});
|
|
22126
|
+
return false;
|
|
22127
|
+
}
|
|
22128
|
+
await this.dispatchIngressCommand(parsed.data);
|
|
22129
|
+
return true;
|
|
22130
|
+
}
|
|
22131
|
+
modelSeenGeneration(agentId) {
|
|
22132
|
+
return this.wakeCoordinator.modelSeenGeneration(agentId);
|
|
22133
|
+
}
|
|
22134
|
+
recordModelSeen(agentId, messages, generation = this.wakeCoordinator.modelSeenGeneration(agentId)) {
|
|
22135
|
+
return this.wakeCoordinator.recordModelSeen(agentId, messages, generation);
|
|
22136
|
+
}
|
|
21828
22137
|
onResync(provider) {
|
|
21829
22138
|
this.resyncProvider = provider;
|
|
21830
22139
|
}
|
|
@@ -21841,6 +22150,7 @@ class WsControlChannel {
|
|
|
21841
22150
|
this.sendFrame({ type: "agent_session", ...info });
|
|
21842
22151
|
}
|
|
21843
22152
|
async reportAgentActivity(info) {
|
|
22153
|
+
this.wakeCoordinator.recordAgentActivity(info.agentId, info.state);
|
|
21844
22154
|
this.sendFrame({ type: "agent_activity", ...info });
|
|
21845
22155
|
}
|
|
21846
22156
|
reportAgentTyping(info) {
|
|
@@ -21853,6 +22163,7 @@ class WsControlChannel {
|
|
|
21853
22163
|
this.sendFrame(frame);
|
|
21854
22164
|
}
|
|
21855
22165
|
async reportWakeAck(info) {
|
|
22166
|
+
this.wakeCoordinator.recordDeliveryAck(info.agentId, info.launchId, info.status);
|
|
21856
22167
|
this.sendFrame({ type: "agent_wake_ack", ...info });
|
|
21857
22168
|
}
|
|
21858
22169
|
async reportStoppedAck(info) {
|
|
@@ -21924,27 +22235,75 @@ class WsControlChannel {
|
|
|
21924
22235
|
return;
|
|
21925
22236
|
}
|
|
21926
22237
|
this.attempt = 0;
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
return;
|
|
21934
|
-
}
|
|
21935
|
-
const cmd = parsed.data;
|
|
22238
|
+
this.ingestCommand(frame).catch((err) => {
|
|
22239
|
+
this.log.warn("command ingress failed", { type: frame.type, err: describeErr(err) });
|
|
22240
|
+
});
|
|
22241
|
+
}
|
|
22242
|
+
async dispatchListeners(cmd) {
|
|
22243
|
+
const pending = [];
|
|
21936
22244
|
for (const cb of this.commandCbs) {
|
|
21937
22245
|
try {
|
|
21938
22246
|
const result = cb(cmd);
|
|
21939
22247
|
if (result === WS_CONTROL_COMMAND_CONSUMED)
|
|
21940
22248
|
break;
|
|
21941
|
-
Promise.resolve(result).catch((err) => {
|
|
22249
|
+
pending.push(Promise.resolve(result).catch((err) => {
|
|
21942
22250
|
this.log.warn("command listener threw", { type: cmd.type, err: describeErr(err) });
|
|
21943
|
-
});
|
|
22251
|
+
}));
|
|
21944
22252
|
} catch (err) {
|
|
21945
22253
|
this.log.warn("command listener threw synchronously", { type: cmd.type, err: describeErr(err) });
|
|
21946
22254
|
}
|
|
21947
22255
|
}
|
|
22256
|
+
await Promise.all(pending);
|
|
22257
|
+
}
|
|
22258
|
+
async dispatchIngressCommand(cmd) {
|
|
22259
|
+
if (cmd.type === "machine:heartbeat") {
|
|
22260
|
+
this.sendFrame({ type: "machine_heartbeat_ack", nonce: cmd.nonce });
|
|
22261
|
+
return;
|
|
22262
|
+
}
|
|
22263
|
+
if (cmd.type === "diagnostics:collect") {
|
|
22264
|
+
this.sendFrame({ type: "diagnostics_ack", reportId: cmd.reportId });
|
|
22265
|
+
await this.dispatchListeners(cmd);
|
|
22266
|
+
return;
|
|
22267
|
+
}
|
|
22268
|
+
if (cmd.type === "agent:wake") {
|
|
22269
|
+
const result = await this.wakeCoordinator.run(cmd, (accepted) => this.dispatchListeners(accepted), (advanced) => {
|
|
22270
|
+
for (const cb of this.wakeDesiredCbs) {
|
|
22271
|
+
try {
|
|
22272
|
+
cb(advanced);
|
|
22273
|
+
} catch (err) {
|
|
22274
|
+
this.log.warn("wake desired observer threw", { err: describeErr(err) });
|
|
22275
|
+
}
|
|
22276
|
+
}
|
|
22277
|
+
});
|
|
22278
|
+
if (result.state === "suppressed") {
|
|
22279
|
+
await this.reportWakeAck({
|
|
22280
|
+
agentId: cmd.agentId,
|
|
22281
|
+
launchId: cmd.launchId,
|
|
22282
|
+
status: "ok"
|
|
22283
|
+
});
|
|
22284
|
+
this.log.debug("duplicate wake suppressed", {
|
|
22285
|
+
agentId: cmd.agentId,
|
|
22286
|
+
channel: cmd.unreadNotice.channel,
|
|
22287
|
+
latestSeq: cmd.unreadNotice.latestSeq,
|
|
22288
|
+
coveredSeq: result.coveredSeq
|
|
22289
|
+
});
|
|
22290
|
+
}
|
|
22291
|
+
return;
|
|
22292
|
+
}
|
|
22293
|
+
if (cmd.type === "machine:reset_all") {
|
|
22294
|
+
for (const reset of cmd.resets)
|
|
22295
|
+
this.wakeCoordinator.invalidate(reset.agentId, true);
|
|
22296
|
+
await this.dispatchListeners(cmd);
|
|
22297
|
+
return;
|
|
22298
|
+
}
|
|
22299
|
+
if (cmd.type === "agent:stop" || cmd.type === "agent:model_switch") {
|
|
22300
|
+
this.wakeCoordinator.invalidate(cmd.agentId, false);
|
|
22301
|
+
} else if (cmd.type === "agent:reset" || cmd.type === "agent:nap") {
|
|
22302
|
+
this.wakeCoordinator.invalidate(cmd.agentId, true);
|
|
22303
|
+
} else if (cmd.type === "bot:removed") {
|
|
22304
|
+
this.wakeCoordinator.invalidate(cmd.botId, true);
|
|
22305
|
+
}
|
|
22306
|
+
await this.dispatchListeners(cmd);
|
|
21948
22307
|
}
|
|
21949
22308
|
onSocketClosed(code, reason) {
|
|
21950
22309
|
this.log.warn("control channel closed", { code, reason: reason ? String(reason) : "" });
|
|
@@ -21979,7 +22338,10 @@ class WsControlChannel {
|
|
|
21979
22338
|
this.pingTimer = setInterval(() => {
|
|
21980
22339
|
if (this.now() > this.pongDeadline) {
|
|
21981
22340
|
this.log.warn("heartbeat pong timeout — forcing reconnect");
|
|
21982
|
-
this.ws?.
|
|
22341
|
+
if (this.ws?.terminate)
|
|
22342
|
+
this.ws.terminate();
|
|
22343
|
+
else
|
|
22344
|
+
this.ws?.close();
|
|
21983
22345
|
return;
|
|
21984
22346
|
}
|
|
21985
22347
|
this.log.debug("heartbeat ping");
|
|
@@ -22131,6 +22493,14 @@ function writeJson(res, status, body) {
|
|
|
22131
22493
|
res.writeHead(status, { "content-type": "application/json" });
|
|
22132
22494
|
res.end(JSON.stringify(body));
|
|
22133
22495
|
}
|
|
22496
|
+
function normalizeContentEncoding(value) {
|
|
22497
|
+
const normalized = (Array.isArray(value) ? value.join(",") : value ?? "").trim().toLowerCase();
|
|
22498
|
+
if (!normalized || normalized === "identity")
|
|
22499
|
+
return "identity";
|
|
22500
|
+
if (normalized === "gzip" || normalized === "deflate" || normalized === "br")
|
|
22501
|
+
return normalized;
|
|
22502
|
+
return "other";
|
|
22503
|
+
}
|
|
22134
22504
|
function isCanonicalChannelScope(channel2) {
|
|
22135
22505
|
try {
|
|
22136
22506
|
const parsed = parseRef(channel2);
|
|
@@ -22227,6 +22597,11 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
22227
22597
|
const upstreamClient = upstream.protocol === "https:" ? https : http;
|
|
22228
22598
|
const onPull = options.onInboxPullResponse;
|
|
22229
22599
|
const onProxyRequest = options.onProxyRequest;
|
|
22600
|
+
const reportInboxPullObservationError = (failure) => {
|
|
22601
|
+
try {
|
|
22602
|
+
options.onInboxPullObservationError?.(failure);
|
|
22603
|
+
} catch {}
|
|
22604
|
+
};
|
|
22230
22605
|
const server = http.createServer((req, res) => {
|
|
22231
22606
|
const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
|
|
22232
22607
|
if (pathname.startsWith("/__alook/local/")) {
|
|
@@ -22265,7 +22640,20 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
22265
22640
|
return;
|
|
22266
22641
|
}
|
|
22267
22642
|
const reg = verdict.reg;
|
|
22268
|
-
const isInboxPull =
|
|
22643
|
+
const isInboxPull = req.method === "POST" && pathname === "/api/community/users/me/inbox/pull";
|
|
22644
|
+
const shouldObserveInboxPull = Boolean(isInboxPull && onPull);
|
|
22645
|
+
let inboxPullObservationToken;
|
|
22646
|
+
if (shouldObserveInboxPull) {
|
|
22647
|
+
try {
|
|
22648
|
+
inboxPullObservationToken = options.onInboxPullStart?.(reg.agentId);
|
|
22649
|
+
} catch {
|
|
22650
|
+
reportInboxPullObservationError({
|
|
22651
|
+
agentId: reg.agentId,
|
|
22652
|
+
reason: "observer_failed",
|
|
22653
|
+
contentEncoding: "identity"
|
|
22654
|
+
});
|
|
22655
|
+
}
|
|
22656
|
+
}
|
|
22269
22657
|
if (onProxyRequest) {
|
|
22270
22658
|
try {
|
|
22271
22659
|
onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
|
|
@@ -22278,6 +22666,8 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
22278
22666
|
outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
|
|
22279
22667
|
outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
|
|
22280
22668
|
outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
|
|
22669
|
+
if (isInboxPull)
|
|
22670
|
+
outHeaders["accept-encoding"] = "identity";
|
|
22281
22671
|
let responded = false;
|
|
22282
22672
|
let upstreamRes;
|
|
22283
22673
|
const upstreamReq = upstreamClient.request({
|
|
@@ -22296,18 +22686,54 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
22296
22686
|
};
|
|
22297
22687
|
res_.on("error", destroyResIfIncomplete);
|
|
22298
22688
|
res_.on("close", destroyResIfIncomplete);
|
|
22299
|
-
if (
|
|
22689
|
+
if (shouldObserveInboxPull && res_.statusCode !== undefined && res_.statusCode >= 200 && res_.statusCode < 300) {
|
|
22300
22690
|
const chunks = [];
|
|
22301
22691
|
res_.on("data", (chunk2) => chunks.push(chunk2));
|
|
22302
22692
|
res_.on("end", () => {
|
|
22303
22693
|
const body = Buffer.concat(chunks);
|
|
22694
|
+
const contentEncoding = normalizeContentEncoding(res_.headers["content-encoding"]);
|
|
22695
|
+
if (contentEncoding !== "identity") {
|
|
22696
|
+
reportInboxPullObservationError({
|
|
22697
|
+
agentId: reg.agentId,
|
|
22698
|
+
reason: "unexpected_content_encoding",
|
|
22699
|
+
contentEncoding
|
|
22700
|
+
});
|
|
22701
|
+
} else {
|
|
22702
|
+
let parsed;
|
|
22703
|
+
try {
|
|
22704
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
22705
|
+
} catch {
|
|
22706
|
+
reportInboxPullObservationError({
|
|
22707
|
+
agentId: reg.agentId,
|
|
22708
|
+
reason: "invalid_json",
|
|
22709
|
+
contentEncoding
|
|
22710
|
+
});
|
|
22711
|
+
}
|
|
22712
|
+
if (parsed !== undefined) {
|
|
22713
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !Array.isArray(parsed.messages)) {
|
|
22714
|
+
reportInboxPullObservationError({
|
|
22715
|
+
agentId: reg.agentId,
|
|
22716
|
+
reason: "invalid_inbox_shape",
|
|
22717
|
+
contentEncoding
|
|
22718
|
+
});
|
|
22719
|
+
} else {
|
|
22720
|
+
const messages = parsed.messages;
|
|
22721
|
+
if (messages.length > 0) {
|
|
22722
|
+
try {
|
|
22723
|
+
onPull(reg.agentId, messages, inboxPullObservationToken);
|
|
22724
|
+
} catch {
|
|
22725
|
+
reportInboxPullObservationError({
|
|
22726
|
+
agentId: reg.agentId,
|
|
22727
|
+
reason: "observer_failed",
|
|
22728
|
+
contentEncoding
|
|
22729
|
+
});
|
|
22730
|
+
}
|
|
22731
|
+
}
|
|
22732
|
+
}
|
|
22733
|
+
}
|
|
22734
|
+
}
|
|
22304
22735
|
res.writeHead(res_.statusCode, res_.headers);
|
|
22305
22736
|
res.end(body);
|
|
22306
|
-
try {
|
|
22307
|
-
const parsed = JSON.parse(body.toString());
|
|
22308
|
-
if (parsed.messages)
|
|
22309
|
-
onPull(reg.agentId, parsed.messages);
|
|
22310
|
-
} catch {}
|
|
22311
22737
|
});
|
|
22312
22738
|
} else {
|
|
22313
22739
|
res.writeHead(res_.statusCode ?? 502, res_.headers);
|
|
@@ -23226,17 +23652,17 @@ function extractToolAudit(rawName, rawInput) {
|
|
|
23226
23652
|
return { name, suppressed: false };
|
|
23227
23653
|
return { name, target: truncateTargetToCodeUnits(firstLine), suppressed: false };
|
|
23228
23654
|
}
|
|
23229
|
-
let
|
|
23655
|
+
let target2;
|
|
23230
23656
|
if (cls === "file_target")
|
|
23231
|
-
|
|
23657
|
+
target2 = pickFileTarget(rawInput);
|
|
23232
23658
|
else if (cls === "pattern")
|
|
23233
|
-
|
|
23659
|
+
target2 = pickPatternTarget(rawInput);
|
|
23234
23660
|
else
|
|
23235
|
-
|
|
23236
|
-
if (typeof
|
|
23661
|
+
target2 = pickFallthroughTarget(rawInput);
|
|
23662
|
+
if (typeof target2 !== "string" || target2.length === 0) {
|
|
23237
23663
|
return { name, suppressed: false };
|
|
23238
23664
|
}
|
|
23239
|
-
return { name, target: truncateTargetToCodeUnits(
|
|
23665
|
+
return { name, target: truncateTargetToCodeUnits(target2), suppressed: false };
|
|
23240
23666
|
}
|
|
23241
23667
|
function truncateThinking(text2) {
|
|
23242
23668
|
const chars = [...text2].length;
|
|
@@ -24031,7 +24457,7 @@ var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. C
|
|
|
24031
24457
|
function buildNapRewakePrompt(handoff) {
|
|
24032
24458
|
return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
|
|
24033
24459
|
|
|
24034
|
-
` + handoff
|
|
24460
|
+
` + handoff + `
|
|
24035
24461
|
|
|
24036
24462
|
Then read @memory.md and your .context_timeline for durable context, and pull ` + "your inbox before doing anything else. If it reports marked messages, run `$ALOOK_CLI " + "message mark list` and resume that outstanding work.";
|
|
24037
24463
|
}
|
|
@@ -24069,6 +24495,7 @@ class AgentRouter {
|
|
|
24069
24495
|
buildReady() {
|
|
24070
24496
|
return {
|
|
24071
24497
|
runtimeReport: [...this.runtimes.values()],
|
|
24498
|
+
capabilities: [CONTROL_HEARTBEAT_CAPABILITY],
|
|
24072
24499
|
runningAgents: [...this.running],
|
|
24073
24500
|
hostname: this.opts.hostname,
|
|
24074
24501
|
platform: this.opts.platform,
|
|
@@ -24911,6 +25338,8 @@ function createTimelineRecorder(opts) {
|
|
|
24911
25338
|
sessionByAgent.set(agentId, sessionId);
|
|
24912
25339
|
},
|
|
24913
25340
|
appendEntryForAgent(agentId, messages) {
|
|
25341
|
+
if (messages.length === 0)
|
|
25342
|
+
return;
|
|
24914
25343
|
const dir = dirFor(agentId);
|
|
24915
25344
|
if (!prepareTimelineDirectory(dir))
|
|
24916
25345
|
return;
|
|
@@ -25330,7 +25759,16 @@ async function createDaemon(opts) {
|
|
|
25330
25759
|
const typingTracker = createTypingScopeTracker();
|
|
25331
25760
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
25332
25761
|
const proxy = await startCredentialProxy(broker, {
|
|
25333
|
-
|
|
25762
|
+
onInboxPullStart: (agentId) => channelRef?.modelSeenGeneration(agentId),
|
|
25763
|
+
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
25764
|
+
timeline2.appendEntryForAgent(agentId, messages);
|
|
25765
|
+
if (typeof observationToken === "number") {
|
|
25766
|
+
channelRef?.recordModelSeen(agentId, messages, observationToken);
|
|
25767
|
+
}
|
|
25768
|
+
},
|
|
25769
|
+
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
25770
|
+
log2.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
|
|
25771
|
+
},
|
|
25334
25772
|
onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
|
|
25335
25773
|
onProxyRequest: (agentId, method, pathname) => {
|
|
25336
25774
|
const subcommand = deriveAuditLogSubcommand(pathname, method);
|
|
@@ -25431,11 +25869,29 @@ async function createDaemon(opts) {
|
|
|
25431
25869
|
if (!res.ok)
|
|
25432
25870
|
throw new Error(`resync-wakes ${res.status}`);
|
|
25433
25871
|
const json2 = await res.json();
|
|
25434
|
-
log2.info("wake resync completed", {
|
|
25872
|
+
log2.info("wake resync completed", { attempted: json2.attempted ?? 0 });
|
|
25435
25873
|
} catch (err) {
|
|
25436
25874
|
log2.warn("wake resync failed", { err: err instanceof Error ? err.message : String(err) });
|
|
25437
25875
|
}
|
|
25438
25876
|
}
|
|
25877
|
+
async function resyncPendingDiagnostics() {
|
|
25878
|
+
try {
|
|
25879
|
+
const res = await fetch(`${opts.serverUrl}/api/community/daemon/resync-diagnostics`, {
|
|
25880
|
+
method: "POST",
|
|
25881
|
+
headers: { authorization: `Bearer ${opts.machineKey}` }
|
|
25882
|
+
});
|
|
25883
|
+
if (!res.ok)
|
|
25884
|
+
throw new Error(`resync-diagnostics ${res.status}`);
|
|
25885
|
+
const json2 = await res.json();
|
|
25886
|
+
log2.info("diagnostics resync completed", {
|
|
25887
|
+
pending: json2.pending ?? 0,
|
|
25888
|
+
attempted: json2.attempted ?? 0,
|
|
25889
|
+
ambiguous: json2.ambiguous ?? 0
|
|
25890
|
+
});
|
|
25891
|
+
} catch (err) {
|
|
25892
|
+
log2.warn("diagnostics resync failed", { err: err instanceof Error ? err.message : String(err) });
|
|
25893
|
+
}
|
|
25894
|
+
}
|
|
25439
25895
|
const enrollAgent = async (agentId) => {
|
|
25440
25896
|
const existing = enrolledKeys.get(agentId);
|
|
25441
25897
|
if (existing)
|
|
@@ -25611,7 +26067,10 @@ async function createDaemon(opts) {
|
|
|
25611
26067
|
let statusTimer = null;
|
|
25612
26068
|
if (opts.statusFilePath) {
|
|
25613
26069
|
const statusPath = opts.statusFilePath;
|
|
25614
|
-
const writeStatus = () =>
|
|
26070
|
+
const writeStatus = () => {
|
|
26071
|
+
const nowMs = Date.now();
|
|
26072
|
+
writeStatusFile(statusPath, { writtenAt: nowMs, agents: manager.statusProjection(nowMs) });
|
|
26073
|
+
};
|
|
25615
26074
|
writeStatus();
|
|
25616
26075
|
statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
|
|
25617
26076
|
statusTimer.unref?.();
|
|
@@ -25658,11 +26117,11 @@ async function createDaemon(opts) {
|
|
|
25658
26117
|
handleDiagnosticCommand: opts.handleDiagnosticCommand,
|
|
25659
26118
|
reportDiagnosticFailure: opts.reportDiagnosticFailure
|
|
25660
26119
|
}));
|
|
26120
|
+
channel2.onWakeDesiredAdvance((cmd) => {
|
|
26121
|
+
reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
|
|
26122
|
+
});
|
|
25661
26123
|
channel2.onCommand((cmd) => {
|
|
25662
26124
|
switch (cmd.type) {
|
|
25663
|
-
case "agent:wake":
|
|
25664
|
-
reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
|
|
25665
|
-
break;
|
|
25666
26125
|
case "agent:stop":
|
|
25667
26126
|
reminderSchedulerRef?.clearAgent(cmd.agentId);
|
|
25668
26127
|
break;
|
|
@@ -25679,6 +26138,7 @@ async function createDaemon(opts) {
|
|
|
25679
26138
|
channel2.onOpen(() => {
|
|
25680
26139
|
coldStartWarmup();
|
|
25681
26140
|
resyncPendingWakes();
|
|
26141
|
+
resyncPendingDiagnostics();
|
|
25682
26142
|
});
|
|
25683
26143
|
channel2.connect();
|
|
25684
26144
|
await router.start();
|
|
@@ -27510,6 +27970,7 @@ var STOP_GRACE_MS = 5000;
|
|
|
27510
27970
|
var STOP_KILL_GRACE_MS = 2000;
|
|
27511
27971
|
var POLL_MS2 = 100;
|
|
27512
27972
|
var START_RECEIPT_TIMEOUT_MS = 15000;
|
|
27973
|
+
var PAIRING_ACTIVATION_TIMEOUT_MS = 30000;
|
|
27513
27974
|
var RUNNER_TERM_GRACE_MS = 2000;
|
|
27514
27975
|
var RUNNER_KILL_GRACE_MS = 2000;
|
|
27515
27976
|
var MACHINE_ID_PATTERN = /^cm_[A-Za-z0-9_-]{8,64}$/;
|
|
@@ -27856,6 +28317,22 @@ function reconcileLegacyMachineKeyOwnership(baseDir, machineKey) {
|
|
|
27856
28317
|
removePidFileIfMatches(candidate, current);
|
|
27857
28318
|
}
|
|
27858
28319
|
}
|
|
28320
|
+
function daemonLastActiveMs(snapshot, nowMs) {
|
|
28321
|
+
const writtenAt = snapshot.writtenAt;
|
|
28322
|
+
if (writtenAt == null || !Number.isFinite(writtenAt) || writtenAt < 0)
|
|
28323
|
+
return null;
|
|
28324
|
+
let latest = null;
|
|
28325
|
+
for (const agent2 of snapshot.agents) {
|
|
28326
|
+
const sinceProgressMs = agent2.sinceProgressMs;
|
|
28327
|
+
if (!Number.isFinite(sinceProgressMs) || sinceProgressMs < 0)
|
|
28328
|
+
continue;
|
|
28329
|
+
const progressAt = writtenAt - sinceProgressMs;
|
|
28330
|
+
if (progressAt <= 0 || progressAt > nowMs)
|
|
28331
|
+
continue;
|
|
28332
|
+
latest = latest == null ? progressAt : Math.max(latest, progressAt);
|
|
28333
|
+
}
|
|
28334
|
+
return latest;
|
|
28335
|
+
}
|
|
27859
28336
|
function daemonList(opts) {
|
|
27860
28337
|
const baseDir = opts.baseDir || process.env.ALOOK_DATA_DIR || DEFAULT_BASE_DIR;
|
|
27861
28338
|
const dir = daemonsDir(baseDir);
|
|
@@ -27879,7 +28356,7 @@ function daemonList(opts) {
|
|
|
27879
28356
|
if (s.found) {
|
|
27880
28357
|
agents = s.agents.length;
|
|
27881
28358
|
running = s.agents.filter((a) => a.derivedActivity === "running").length;
|
|
27882
|
-
lastActiveMs = s
|
|
28359
|
+
lastActiveMs = daemonLastActiveMs(s, now);
|
|
27883
28360
|
}
|
|
27884
28361
|
}
|
|
27885
28362
|
results.push({ id, pid: data.pid, alive, agents, running, lastActiveMs });
|
|
@@ -28036,20 +28513,58 @@ function findExistingCredentialForBearer(baseDir, bearer) {
|
|
|
28036
28513
|
}
|
|
28037
28514
|
return null;
|
|
28038
28515
|
}
|
|
28039
|
-
async function activatePairingToken(serverUrl, tokenId, hostname4, platform, arch, osRelease, daemonVersion, runtimeReport) {
|
|
28040
|
-
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
});
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
28516
|
+
async function activatePairingToken(serverUrl, tokenId, hostname4, platform, arch, osRelease, daemonVersion, runtimeReport, expectedMachineId) {
|
|
28517
|
+
let res;
|
|
28518
|
+
let json2;
|
|
28519
|
+
let timedOut = false;
|
|
28520
|
+
const controller = new AbortController;
|
|
28521
|
+
const timeout = setTimeout(() => {
|
|
28522
|
+
timedOut = true;
|
|
28523
|
+
controller.abort();
|
|
28524
|
+
}, PAIRING_ACTIVATION_TIMEOUT_MS);
|
|
28525
|
+
timeout.unref?.();
|
|
28526
|
+
try {
|
|
28527
|
+
res = await fetch(`${serverUrl}/api/community/daemon/activate`, {
|
|
28528
|
+
method: "POST",
|
|
28529
|
+
headers: {
|
|
28530
|
+
"content-type": "application/json",
|
|
28531
|
+
authorization: `Bearer ${tokenId}`
|
|
28532
|
+
},
|
|
28533
|
+
body: JSON.stringify({
|
|
28534
|
+
hostname: hostname4,
|
|
28535
|
+
platform,
|
|
28536
|
+
arch,
|
|
28537
|
+
osRelease,
|
|
28538
|
+
daemonVersion,
|
|
28539
|
+
runtimeReport,
|
|
28540
|
+
...expectedMachineId ? { expectedMachineId } : {}
|
|
28541
|
+
}),
|
|
28542
|
+
signal: controller.signal
|
|
28543
|
+
});
|
|
28544
|
+
json2 = await res.json().catch(() => null);
|
|
28545
|
+
} catch (error51) {
|
|
28546
|
+
throw new PairingActivationError(timedOut ? "activation timed out before commit status was known" : error51 instanceof Error ? error51.message : String(error51), "unknown");
|
|
28547
|
+
} finally {
|
|
28548
|
+
clearTimeout(timeout);
|
|
28549
|
+
}
|
|
28550
|
+
if (!res.ok) {
|
|
28551
|
+
const parsed2 = CommunityDaemonActivateErrorResponseSchema.safeParse(json2);
|
|
28552
|
+
throw new PairingActivationError(parsed2.success ? parsed2.data.error : `activate failed (${res.status})`, res.status >= 400 && res.status < 500 && parsed2.success ? parsed2.data.sessionOutcome : "unknown");
|
|
28553
|
+
}
|
|
28554
|
+
const parsed = CommunityDaemonActivateResponseSchema.safeParse(json2);
|
|
28555
|
+
if (!parsed.success) {
|
|
28556
|
+
throw new PairingActivationError("activate returned an invalid response", "unknown");
|
|
28557
|
+
}
|
|
28558
|
+
return { credential: parsed.data.credential, machineId: parsed.data.machineId };
|
|
28559
|
+
}
|
|
28560
|
+
|
|
28561
|
+
class PairingActivationError extends Error {
|
|
28562
|
+
sessionOutcome;
|
|
28563
|
+
constructor(message2, sessionOutcome) {
|
|
28564
|
+
super(message2);
|
|
28565
|
+
this.sessionOutcome = sessionOutcome;
|
|
28566
|
+
this.name = "PairingActivationError";
|
|
28051
28567
|
}
|
|
28052
|
-
return { credential: json2.credential, machineId: json2.machineId };
|
|
28053
28568
|
}
|
|
28054
28569
|
async function resolveMachineIdentity(serverUrl, credential) {
|
|
28055
28570
|
const response = await fetch(`${serverUrl}/api/community/daemon/identity`, {
|
|
@@ -28077,12 +28592,13 @@ async function prepareDaemonStart(opts) {
|
|
|
28077
28592
|
throw new Error("daemon package version is not a strict release version");
|
|
28078
28593
|
const ownerToken = crypto5.randomBytes(24).toString("base64url");
|
|
28079
28594
|
const persisted = opts.machineKey.startsWith("cmk_") ? findExistingCredentialForBearer(baseDir, opts.machineKey) : null;
|
|
28595
|
+
const reconnectMachineId = opts.machineKey.startsWith("cmt_") && opts.expectedMachineId && opts.resumeRequestId ? validateMachineId(opts.expectedMachineId) : undefined;
|
|
28080
28596
|
let coarseLockPath = null;
|
|
28081
28597
|
let launchLockPath = null;
|
|
28082
28598
|
try {
|
|
28083
28599
|
let machineKey = persisted?.credential;
|
|
28084
|
-
let machineId = persisted ? validateMachineId(persisted.machineId) :
|
|
28085
|
-
if (
|
|
28600
|
+
let machineId = persisted ? validateMachineId(persisted.machineId) : reconnectMachineId;
|
|
28601
|
+
if (machineId) {
|
|
28086
28602
|
launchLockPath = acquireLaunchLock(baseDir, machineId, ownerToken, opts.resumeRequestId);
|
|
28087
28603
|
} else {
|
|
28088
28604
|
coarseLockPath = acquireCoarseLock(baseDir, ownerToken);
|
|
@@ -28093,9 +28609,13 @@ async function prepareDaemonStart(opts) {
|
|
|
28093
28609
|
const runtimeReport = await detectRuntimes();
|
|
28094
28610
|
const healthyRuntimeIds = runtimeReport.filter((runtime) => runtime.status === "healthy").map((runtime) => runtime.id);
|
|
28095
28611
|
if (opts.machineKey.startsWith("cmt_")) {
|
|
28096
|
-
|
|
28612
|
+
opts.onActivationAttempt?.();
|
|
28613
|
+
const activated = await activatePairingToken(serverUrl, opts.machineKey, os3.hostname(), process.platform, process.arch, os3.release(), daemonVersion, runtimeReport, opts.expectedMachineId);
|
|
28097
28614
|
machineKey = activated.credential;
|
|
28098
28615
|
machineId = validateMachineId(activated.machineId);
|
|
28616
|
+
if (opts.expectedMachineId && machineId !== validateMachineId(opts.expectedMachineId)) {
|
|
28617
|
+
throw new Error("activate returned a different machine identity");
|
|
28618
|
+
}
|
|
28099
28619
|
} else {
|
|
28100
28620
|
machineKey ??= opts.machineKey;
|
|
28101
28621
|
machineId ??= validateMachineId(await resolveMachineIdentity(serverUrl, opts.machineKey));
|
|
@@ -28109,6 +28629,7 @@ async function prepareDaemonStart(opts) {
|
|
|
28109
28629
|
wsUrl,
|
|
28110
28630
|
daemonVersion
|
|
28111
28631
|
});
|
|
28632
|
+
if (false) {}
|
|
28112
28633
|
if (coarseLockPath)
|
|
28113
28634
|
removeOwnedFile(coarseLockPath, process.pid, ownerToken);
|
|
28114
28635
|
const startedAt = new Date().toISOString();
|
|
@@ -28167,6 +28688,66 @@ async function daemonStartById(opts) {
|
|
|
28167
28688
|
foreground: opts.foreground
|
|
28168
28689
|
});
|
|
28169
28690
|
}
|
|
28691
|
+
function pidOwnershipEqual(a, b) {
|
|
28692
|
+
if (!a || !b)
|
|
28693
|
+
return a === b;
|
|
28694
|
+
return a.pid === b.pid && a.machineId === b.machineId && a.startedAt === b.startedAt && a.ownerToken === b.ownerToken;
|
|
28695
|
+
}
|
|
28696
|
+
async function daemonReconnect(opts, deps = {}) {
|
|
28697
|
+
if (!opts.machineKey.startsWith("cmt_")) {
|
|
28698
|
+
throw new Error("daemon reconnect requires a cmt_ pairing token");
|
|
28699
|
+
}
|
|
28700
|
+
const machineId = validateMachineId(opts.id);
|
|
28701
|
+
const baseDir = opts.baseDir || process.env.ALOOK_DATA_DIR || DEFAULT_BASE_DIR;
|
|
28702
|
+
const launch = readDaemonLaunchRecord(baseDir, machineId);
|
|
28703
|
+
const pidPath = pidfilePathById(baseDir, machineId);
|
|
28704
|
+
const before = readPidFile(pidPath);
|
|
28705
|
+
if (before && (before.machineId !== machineId || !before.startedAt || !before.ownerToken)) {
|
|
28706
|
+
throw new Error("daemon reconnect cannot verify the current pid ownership tuple");
|
|
28707
|
+
}
|
|
28708
|
+
const requestId = crypto5.randomUUID();
|
|
28709
|
+
const replacement = acquireDaemonReplacementLock({ baseDir, machineId, requestId });
|
|
28710
|
+
let oldStopped = false;
|
|
28711
|
+
let activationAttempted = false;
|
|
28712
|
+
try {
|
|
28713
|
+
const owned = readPidFile(pidPath);
|
|
28714
|
+
if (!pidOwnershipEqual(before, owned)) {
|
|
28715
|
+
throw new Error("daemon ownership changed after reconnect lock acquisition");
|
|
28716
|
+
}
|
|
28717
|
+
if (owned && (deps.isProcessAlive ?? isProcessAlive)(owned.pid)) {
|
|
28718
|
+
await (deps.stopExactDaemonPid ?? stopExactDaemonPid)(owned.pid);
|
|
28719
|
+
oldStopped = true;
|
|
28720
|
+
removePidFileIfMatches(pidPath, owned);
|
|
28721
|
+
} else if (owned) {
|
|
28722
|
+
removePidFileIfMatches(pidPath, owned);
|
|
28723
|
+
}
|
|
28724
|
+
try {
|
|
28725
|
+
await (deps.start ?? daemonStart)({
|
|
28726
|
+
machineKey: opts.machineKey,
|
|
28727
|
+
serverUrl: opts.serverUrl ?? launch.serverUrl,
|
|
28728
|
+
wsUrl: opts.wsUrl ?? launch.wsUrl,
|
|
28729
|
+
baseDir,
|
|
28730
|
+
resumeRequestId: requestId,
|
|
28731
|
+
expectedMachineId: machineId,
|
|
28732
|
+
onActivationAttempt: () => {
|
|
28733
|
+
activationAttempted = true;
|
|
28734
|
+
}
|
|
28735
|
+
});
|
|
28736
|
+
} catch (error51) {
|
|
28737
|
+
const canRestoreOldEpoch = !activationAttempted || error51 instanceof PairingActivationError && error51.sessionOutcome === "not_committed";
|
|
28738
|
+
if (oldStopped && canRestoreOldEpoch) {
|
|
28739
|
+
try {
|
|
28740
|
+
await (deps.resume ?? daemonResume)({ id: machineId, baseDir, requestId });
|
|
28741
|
+
} catch (rollbackError) {
|
|
28742
|
+
throw new AggregateError([error51, rollbackError], "reconnect failed before rotation and the prior daemon could not be resumed");
|
|
28743
|
+
}
|
|
28744
|
+
}
|
|
28745
|
+
throw error51;
|
|
28746
|
+
}
|
|
28747
|
+
} finally {
|
|
28748
|
+
removeReplacementLockIfMatches(replacement.path, replacement.lock);
|
|
28749
|
+
}
|
|
28750
|
+
}
|
|
28170
28751
|
function runnerArguments() {
|
|
28171
28752
|
const command = process.env.ALOOK_DAEMON_PACKAGE_WRAPPER === "1" ? ["run"] : ["daemon", "run"];
|
|
28172
28753
|
return [...process.execArgv, process.argv[1], ...command];
|
|
@@ -28502,7 +29083,38 @@ var TEXT_ESCAPE_MAP = { n: `
|
|
|
28502
29083
|
function decodeTextEscapes(s) {
|
|
28503
29084
|
return s.replace(/\\(.)/g, (m, c) => TEXT_ESCAPE_MAP[c] ?? m);
|
|
28504
29085
|
}
|
|
28505
|
-
|
|
29086
|
+
async function readLiteralInput(args) {
|
|
29087
|
+
const { command, stdinSelected, stdin, filePath, fileOption } = args;
|
|
29088
|
+
if (stdinSelected && filePath !== undefined) {
|
|
29089
|
+
throw new CliError(`${command}: --stdin and ${fileOption} are mutually exclusive`);
|
|
29090
|
+
}
|
|
29091
|
+
if (stdinSelected) {
|
|
29092
|
+
if (!stdin)
|
|
29093
|
+
throw new CliError(`${command}: stdin is unavailable`);
|
|
29094
|
+
if (stdin.isTTY === true) {
|
|
29095
|
+
throw new CliError(`${command}: --stdin requires piped input; use ${fileOption} in an interactive terminal`);
|
|
29096
|
+
}
|
|
29097
|
+
try {
|
|
29098
|
+
const chunks = [];
|
|
29099
|
+
for await (const chunk2 of stdin) {
|
|
29100
|
+
chunks.push(typeof chunk2 === "string" ? Buffer.from(chunk2, "utf8") : Buffer.from(chunk2));
|
|
29101
|
+
}
|
|
29102
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
29103
|
+
} catch (err) {
|
|
29104
|
+
throw new CliError(`${command}: cannot read stdin: ${err.message}`);
|
|
29105
|
+
}
|
|
29106
|
+
}
|
|
29107
|
+
if (filePath !== undefined) {
|
|
29108
|
+
const fs13 = await import("fs/promises");
|
|
29109
|
+
try {
|
|
29110
|
+
return await fs13.readFile(filePath, "utf8");
|
|
29111
|
+
} catch (err) {
|
|
29112
|
+
throw new CliError(`${command}: cannot read file: ${err.message}`);
|
|
29113
|
+
}
|
|
29114
|
+
}
|
|
29115
|
+
return;
|
|
29116
|
+
}
|
|
29117
|
+
var CLIENT_MAX_ATTACHMENT_BYTES = 26214400;
|
|
28506
29118
|
function contentTypeFromFilename(filename) {
|
|
28507
29119
|
const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
|
|
28508
29120
|
switch (ext) {
|
|
@@ -28560,7 +29172,7 @@ async function withTransientMutationRetry(mutation) {
|
|
|
28560
29172
|
async function sendWithRetry(api2, req) {
|
|
28561
29173
|
return withTransientMutationRetry(() => api2.send(req));
|
|
28562
29174
|
}
|
|
28563
|
-
async function cmdMessageSend(opts) {
|
|
29175
|
+
async function cmdMessageSend(opts, stdin) {
|
|
28564
29176
|
const remindAfterFlag = opts.remindAfter;
|
|
28565
29177
|
const remindAfterMs = remindAfterFlag === undefined ? undefined : parseRemindAfter(remindAfterFlag);
|
|
28566
29178
|
const api2 = getApi();
|
|
@@ -28568,21 +29180,18 @@ async function cmdMessageSend(opts) {
|
|
|
28568
29180
|
const channel2 = opts.target;
|
|
28569
29181
|
if (!channel2)
|
|
28570
29182
|
throw new CliError("message send: --target <ref> is required (e.g. /demo-workspace#1234/general)");
|
|
28571
|
-
let text2;
|
|
28572
29183
|
const fileFlag = opts.file;
|
|
28573
|
-
const
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
|
|
28579
|
-
}
|
|
28580
|
-
text2 = decodeTextEscapes(textFlag);
|
|
28581
|
-
}
|
|
29184
|
+
const text2 = await readLiteralInput({
|
|
29185
|
+
command: "message send",
|
|
29186
|
+
stdinSelected: opts.stdin === true,
|
|
29187
|
+
stdin,
|
|
29188
|
+
filePath: fileFlag,
|
|
29189
|
+
fileOption: "--file <path>"
|
|
29190
|
+
});
|
|
28582
29191
|
const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
|
|
28583
29192
|
const hasText = typeof text2 === "string" && text2.trim().length > 0;
|
|
28584
29193
|
if (!hasText && attachmentIds.length === 0) {
|
|
28585
|
-
throw new CliError("message send: --
|
|
29194
|
+
throw new CliError("message send: --stdin, --file <path>, or --attachment <id> is required");
|
|
28586
29195
|
}
|
|
28587
29196
|
let replyToSeq;
|
|
28588
29197
|
const replyFlag = opts.reply;
|
|
@@ -28595,7 +29204,7 @@ async function cmdMessageSend(opts) {
|
|
|
28595
29204
|
}
|
|
28596
29205
|
replyToSeq = n;
|
|
28597
29206
|
}
|
|
28598
|
-
const nonce =
|
|
29207
|
+
const nonce = randomUUID7();
|
|
28599
29208
|
const res = await sendWithRetry(api2, {
|
|
28600
29209
|
agentId: agent2,
|
|
28601
29210
|
channel: channel2,
|
|
@@ -28626,71 +29235,32 @@ async function cmdMessageSend(opts) {
|
|
|
28626
29235
|
return { sent, reminder: { armed: false, reason: "local reminder request failed" } };
|
|
28627
29236
|
}
|
|
28628
29237
|
}
|
|
28629
|
-
async function createPostWithRetry(api2, req) {
|
|
28630
|
-
return withTransientMutationRetry(() => api2.createPost(req));
|
|
28631
|
-
}
|
|
28632
|
-
async function cmdMessagePost(opts) {
|
|
28633
|
-
const api2 = getApi();
|
|
28634
|
-
const agent2 = agentId(opts);
|
|
28635
|
-
const forum = opts.target;
|
|
28636
|
-
if (!forum)
|
|
28637
|
-
throw new CliError("message post: --target <forum-ref> is required (e.g. /demo#1234/ideas)");
|
|
28638
|
-
const title = opts.title;
|
|
28639
|
-
if (!title || title.trim().length === 0)
|
|
28640
|
-
throw new CliError("message post: --title <name> is required");
|
|
28641
|
-
let text2;
|
|
28642
|
-
const fileFlag = opts.file;
|
|
28643
|
-
const textFlag = opts.text;
|
|
28644
|
-
if (fileFlag) {
|
|
28645
|
-
const fs13 = await import("fs");
|
|
28646
|
-
if (!fs13.existsSync(fileFlag))
|
|
28647
|
-
throw new CliError(`message post: file not found: ${fileFlag}`);
|
|
28648
|
-
text2 = fs13.readFileSync(fileFlag, "utf8").trim();
|
|
28649
|
-
} else if (typeof textFlag === "string") {
|
|
28650
|
-
text2 = decodeTextEscapes(textFlag);
|
|
28651
|
-
}
|
|
28652
|
-
const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
|
|
28653
|
-
const hasText = typeof text2 === "string" && text2.trim().length > 0;
|
|
28654
|
-
if (!hasText && attachmentIds.length === 0) {
|
|
28655
|
-
throw new CliError("message post: --text <text>, --file <path>, or --attachment <id> is required");
|
|
28656
|
-
}
|
|
28657
|
-
const nonce = randomUUID6();
|
|
28658
|
-
const res = await createPostWithRetry(api2, {
|
|
28659
|
-
agentId: agent2,
|
|
28660
|
-
forum,
|
|
28661
|
-
title: title.trim(),
|
|
28662
|
-
content: { text: text2 ?? "" },
|
|
28663
|
-
attachments: attachmentIds.length > 0 ? attachmentIds : undefined,
|
|
28664
|
-
nonce
|
|
28665
|
-
});
|
|
28666
|
-
return { posted: res.ref };
|
|
28667
|
-
}
|
|
28668
29238
|
async function cmdMessageEmoji(opts) {
|
|
28669
29239
|
const api2 = getApi();
|
|
28670
|
-
const
|
|
29240
|
+
const target2 = opts.target;
|
|
28671
29241
|
const emoji3 = opts.emoji;
|
|
28672
|
-
if (!
|
|
29242
|
+
if (!target2)
|
|
28673
29243
|
throw new CliError("message emoji: --target <ref> is required (e.g. /demo#1234/general#42)");
|
|
28674
29244
|
if (!emoji3)
|
|
28675
29245
|
throw new CliError("message emoji: --emoji <string> is required");
|
|
28676
|
-
const { channel: channel2, seq } = parseMessageTarget("message emoji",
|
|
29246
|
+
const { channel: channel2, seq } = parseMessageTarget("message emoji", target2);
|
|
28677
29247
|
if (Buffer.byteLength(emoji3, "utf8") > MAX_EMOJI_BYTES) {
|
|
28678
29248
|
const err = new CliError("emoji is too long");
|
|
28679
29249
|
err.hint = "use a single emoji, not a phrase";
|
|
28680
29250
|
throw err;
|
|
28681
29251
|
}
|
|
28682
29252
|
const res = await api2.reactAdd({ channel: channel2, seq, emoji: emoji3 });
|
|
28683
|
-
return { target, emoji: emoji3, duplicate: res.duplicate === true };
|
|
29253
|
+
return { target: target2, emoji: emoji3, duplicate: res.duplicate === true };
|
|
28684
29254
|
}
|
|
28685
|
-
function parseMessageTarget(command,
|
|
29255
|
+
function parseMessageTarget(command, target2) {
|
|
28686
29256
|
let parsed;
|
|
28687
29257
|
try {
|
|
28688
|
-
parsed = parseRef(
|
|
29258
|
+
parsed = parseRef(target2);
|
|
28689
29259
|
} catch (err) {
|
|
28690
29260
|
throw new CliError(`${command}: ${err.message}`);
|
|
28691
29261
|
}
|
|
28692
29262
|
if (parsed.seq === undefined) {
|
|
28693
|
-
const err = new CliError(`${command} needs a ref with a seq (e.g. ${
|
|
29263
|
+
const err = new CliError(`${command} needs a ref with a seq (e.g. ${target2}#42)`);
|
|
28694
29264
|
err.hint = "pass --target /<server>/<channel>#N, /<server>/<channel>/#N#M for thread reply, or /.dm/<peer>#N";
|
|
28695
29265
|
throw err;
|
|
28696
29266
|
}
|
|
@@ -28699,21 +29269,21 @@ function parseMessageTarget(command, target) {
|
|
|
28699
29269
|
}
|
|
28700
29270
|
async function cmdMessageMarkSet(opts) {
|
|
28701
29271
|
const api2 = getApi();
|
|
28702
|
-
const
|
|
28703
|
-
if (!
|
|
29272
|
+
const target2 = opts.target;
|
|
29273
|
+
if (!target2)
|
|
28704
29274
|
throw new CliError("message mark set: --target <ref> is required");
|
|
28705
|
-
const request = parseMessageTarget("message mark set",
|
|
29275
|
+
const request = parseMessageTarget("message mark set", target2);
|
|
28706
29276
|
await withTransientMutationRetry(() => api2.markSet(request));
|
|
28707
|
-
return { target, marked: true };
|
|
29277
|
+
return { target: target2, marked: true };
|
|
28708
29278
|
}
|
|
28709
29279
|
async function cmdMessageMarkRemove(opts) {
|
|
28710
29280
|
const api2 = getApi();
|
|
28711
|
-
const
|
|
28712
|
-
if (!
|
|
29281
|
+
const target2 = opts.target;
|
|
29282
|
+
if (!target2)
|
|
28713
29283
|
throw new CliError("message mark remove: --target <ref> is required");
|
|
28714
|
-
const request = parseMessageTarget("message mark remove",
|
|
29284
|
+
const request = parseMessageTarget("message mark remove", target2);
|
|
28715
29285
|
await withTransientMutationRetry(() => api2.markRemove(request));
|
|
28716
|
-
return { target, marked: false };
|
|
29286
|
+
return { target: target2, marked: false };
|
|
28717
29287
|
}
|
|
28718
29288
|
async function cmdMessageMarkList(opts) {
|
|
28719
29289
|
const api2 = getApi();
|
|
@@ -28723,9 +29293,9 @@ async function cmdMessageMarkList(opts) {
|
|
|
28723
29293
|
async function cmdAttachmentUpload(opts) {
|
|
28724
29294
|
const api2 = getApi();
|
|
28725
29295
|
const agent2 = agentId(opts);
|
|
28726
|
-
const
|
|
29296
|
+
const target2 = opts.target;
|
|
28727
29297
|
const filePath = opts.file;
|
|
28728
|
-
if (!
|
|
29298
|
+
if (!target2)
|
|
28729
29299
|
throw new CliError("message attachment upload: --target <ref> is required");
|
|
28730
29300
|
if (!filePath)
|
|
28731
29301
|
throw new CliError("message attachment upload: --file <path> is required");
|
|
@@ -28766,7 +29336,7 @@ async function cmdAttachmentUpload(opts) {
|
|
|
28766
29336
|
}
|
|
28767
29337
|
const result = await api2.attachmentUpload({
|
|
28768
29338
|
agentId: agent2,
|
|
28769
|
-
target,
|
|
29339
|
+
target: target2,
|
|
28770
29340
|
file: { data: new Uint8Array(bytes), filename, contentType },
|
|
28771
29341
|
...thumbnail ? { thumbnail } : {},
|
|
28772
29342
|
...width !== undefined ? { width } : {},
|
|
@@ -28846,6 +29416,7 @@ async function cmdInboxPull(opts) {
|
|
|
28846
29416
|
const pulledAt = nowLocalISO();
|
|
28847
29417
|
let acked = 0;
|
|
28848
29418
|
let ackError;
|
|
29419
|
+
let failed;
|
|
28849
29420
|
if (opts.ack !== false && messages.length > 0) {
|
|
28850
29421
|
const latest = new Map;
|
|
28851
29422
|
for (const m of messages) {
|
|
@@ -28855,8 +29426,9 @@ async function cmdInboxPull(opts) {
|
|
|
28855
29426
|
latest.set(m.channel, { channel: m.channel, seq: seqN });
|
|
28856
29427
|
}
|
|
28857
29428
|
try {
|
|
28858
|
-
await api2.ack({ agentId: agent2, cursors: [...latest.values()] });
|
|
28859
|
-
acked =
|
|
29429
|
+
const result = await api2.ack({ agentId: agent2, cursors: [...latest.values()] });
|
|
29430
|
+
acked = result.applied.length;
|
|
29431
|
+
failed = result.failed;
|
|
28860
29432
|
} catch (err) {
|
|
28861
29433
|
ackError = err instanceof Error ? err.message : String(err);
|
|
28862
29434
|
}
|
|
@@ -28866,6 +29438,7 @@ async function cmdInboxPull(opts) {
|
|
|
28866
29438
|
hasMore,
|
|
28867
29439
|
acked,
|
|
28868
29440
|
pulledAt,
|
|
29441
|
+
...failed ? { failed } : {},
|
|
28869
29442
|
...ackError ? { ackError } : {},
|
|
28870
29443
|
...markedCount > 0 ? {
|
|
28871
29444
|
markedReminder: `You have ${markedCount} marked ${markedCount === 1 ? "message" : "messages"}. Resolve ${markedCount === 1 ? "it" : "them"} before going dark unless blocked.`
|
|
@@ -28961,38 +29534,28 @@ async function cmdFriendList(opts) {
|
|
|
28961
29534
|
async function cmdNap(opts) {
|
|
28962
29535
|
const api2 = getApi();
|
|
28963
29536
|
const fileFlag = opts.handoff;
|
|
28964
|
-
const
|
|
28965
|
-
|
|
28966
|
-
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
28970
|
-
|
|
28971
|
-
|
|
28972
|
-
handoff = decodeTextEscapes(textFlag).trim();
|
|
28973
|
-
}
|
|
28974
|
-
if (!handoff) {
|
|
28975
|
-
throw new CliError("nap: a handoff is required — pass --handoff <file> or --text <note>");
|
|
29537
|
+
const handoff = await readLiteralInput({
|
|
29538
|
+
command: "nap",
|
|
29539
|
+
stdinSelected: false,
|
|
29540
|
+
filePath: fileFlag,
|
|
29541
|
+
fileOption: "--handoff <file>"
|
|
29542
|
+
});
|
|
29543
|
+
if (handoff === undefined || handoff.trim().length === 0) {
|
|
29544
|
+
throw new CliError("nap: a handoff is required — pass --handoff <file>");
|
|
28976
29545
|
}
|
|
28977
29546
|
return await api2.nap({ handoff });
|
|
28978
29547
|
}
|
|
28979
|
-
function buildProgram() {
|
|
29548
|
+
function buildProgram(stdin) {
|
|
28980
29549
|
const program = new Command("alook").description("agent CLI").exitOverride().configureOutput({
|
|
28981
29550
|
writeOut: () => {},
|
|
28982
29551
|
writeErr: () => {}
|
|
28983
29552
|
}).option("--agent <id>", "agent identity (or ALOOK_AGENT_ID env)");
|
|
28984
29553
|
const message2 = program.command("message").description("message operations").exitOverride();
|
|
28985
29554
|
message2.configureOutput({ writeOut: () => {}, writeErr: () => {} });
|
|
28986
|
-
message2.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace#1234/general)").option("--
|
|
28987
|
-
const localOpts = this.opts();
|
|
28988
|
-
const globalOpts = program.opts();
|
|
28989
|
-
const result = await cmdMessageSend({ ...globalOpts, ...localOpts });
|
|
28990
|
-
printEnvelope({ success: result });
|
|
28991
|
-
});
|
|
28992
|
-
message2.command("post").description("create a new forum post in a forum").option("--target <forum-ref>", "the forum to post in (path-style ref, e.g. /demo#1234/ideas)").option("--title <name>", "the post title (its slug becomes the post's address)").option("--text <text>", "inline post body (short)").option("--file <path>", "read post body from a file (long)").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = body order)", (v, prev = []) => [...prev, v], []).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
29555
|
+
message2.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace#1234/general)").option("--stdin", "read the literal UTF-8 message body from non-TTY stdin").option("--file <path>", "read the literal UTF-8 message body from a file").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = message order)", (v, prev = []) => [...prev, v], []).option("--reply <seq>", 'reply to a message by its seq in --target (e.g. "#37" or 37)').option("--remind-after <duration>", "optionally arm one local follow-up wake after 1m..24h; a newer same-scope message or daemon restart cancels it").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
28993
29556
|
const localOpts = this.opts();
|
|
28994
29557
|
const globalOpts = program.opts();
|
|
28995
|
-
const result = await
|
|
29558
|
+
const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
|
|
28996
29559
|
printEnvelope({ success: result });
|
|
28997
29560
|
});
|
|
28998
29561
|
message2.command("emoji").description("react to a message with a single emoji").requiredOption("--target <ref>", "message ref (path-style, e.g. /demo#1234/general#42 or /.dm/peer#0007#42)").requiredOption("--emoji <string>", "single emoji character").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
@@ -29097,7 +29660,7 @@ function buildProgram() {
|
|
|
29097
29660
|
const result = await cmdSettingProfile({ ...program.opts(), ...this.opts() });
|
|
29098
29661
|
printEnvelope({ success: result });
|
|
29099
29662
|
});
|
|
29100
|
-
program.command("nap").description("end your session and start fresh, carrying a handoff to your reborn self (read the nap rule first)").option("--handoff <file>", "path to your handoff note (your note to your reborn self)").
|
|
29663
|
+
program.command("nap").description("end your session and start fresh, carrying a handoff to your reborn self (read the nap rule first)").option("--handoff <file>", "path to your handoff note (your note to your reborn self)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
29101
29664
|
const localOpts = this.opts();
|
|
29102
29665
|
const globalOpts = program.opts();
|
|
29103
29666
|
const result = await cmdNap({ ...globalOpts, ...localOpts });
|
|
@@ -29131,6 +29694,16 @@ function buildProgram() {
|
|
|
29131
29694
|
daemon.command("run", { hidden: true }).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async () => {
|
|
29132
29695
|
await daemonRunFromIpc();
|
|
29133
29696
|
});
|
|
29697
|
+
daemon.command("reconnect").description("rotate and reconnect one previously paired machine").requiredOption("--id <machineId>", "machine id shown by daemon list").requiredOption("--machine-key <key>", "cmt_ reconnect token").option("--server-url <url>", "server HTTP URL (defaults to the saved launch record)").option("--ws-url <url>", "server WebSocket URL (defaults to the saved launch record)").option("--base-dir <path>", "data directory for agent workspaces and pidfile").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
29698
|
+
const localOpts = this.opts();
|
|
29699
|
+
await daemonReconnect({
|
|
29700
|
+
id: localOpts.id,
|
|
29701
|
+
machineKey: localOpts.machineKey,
|
|
29702
|
+
serverUrl: localOpts.serverUrl,
|
|
29703
|
+
wsUrl: localOpts.wsUrl,
|
|
29704
|
+
baseDir: localOpts.baseDir
|
|
29705
|
+
});
|
|
29706
|
+
});
|
|
29134
29707
|
daemon.command("resume", { hidden: true }).requiredOption("--id <machineId>").requiredOption("--base-dir <path>").requiredOption("--request-id <id>").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
29135
29708
|
const localOpts = this.opts();
|
|
29136
29709
|
await daemonResume({
|
|
@@ -29178,8 +29751,9 @@ function buildProgram() {
|
|
|
29178
29751
|
});
|
|
29179
29752
|
return program;
|
|
29180
29753
|
}
|
|
29181
|
-
async function main(argv = process.argv.slice(2)) {
|
|
29182
|
-
const
|
|
29754
|
+
async function main(argv = process.argv.slice(2), io = {}) {
|
|
29755
|
+
const stdin = io.stdin ?? process.stdin;
|
|
29756
|
+
const program = buildProgram(stdin);
|
|
29183
29757
|
let internalExitCode = 0;
|
|
29184
29758
|
try {
|
|
29185
29759
|
await program.parseAsync(argv, { from: "user" });
|