@alook/daemon 0.1.10 → 0.1.12
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/cli/index.js +135 -142
- package/dist/index.js +17 -10
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -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
|
|
|
@@ -17576,13 +17612,6 @@ var CommunityAgentResolveRequestSchema = exports_external.object({
|
|
|
17576
17612
|
var CommunityAgentListChannelsRequestSchema = exports_external.object({
|
|
17577
17613
|
server: exports_external.string().min(1).optional()
|
|
17578
17614
|
});
|
|
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
17615
|
var CommunityAgentListMembersRequestSchema = exports_external.object({
|
|
17587
17616
|
server: exports_external.string().min(1),
|
|
17588
17617
|
limit: exports_external.number().int().positive().optional(),
|
|
@@ -17596,7 +17625,9 @@ var CommunityAgentJoinServerRequestSchema = exports_external.object({
|
|
|
17596
17625
|
invite: exports_external.string().min(1)
|
|
17597
17626
|
});
|
|
17598
17627
|
var CommunityAgentNapRequestSchema = exports_external.object({
|
|
17599
|
-
handoff: exports_external.string().
|
|
17628
|
+
handoff: exports_external.string().refine((value) => value.trim().length > 0, {
|
|
17629
|
+
message: "handoff is required"
|
|
17630
|
+
})
|
|
17600
17631
|
});
|
|
17601
17632
|
var CommunityAgentReactAddRequestSchema = exports_external.object({
|
|
17602
17633
|
channel: exports_external.string().min(1),
|
|
@@ -17748,6 +17779,7 @@ var communityChannel = sqliteTable("community_channel", {
|
|
|
17748
17779
|
index("idx_channel_server_position").on(t.serverId, t.position),
|
|
17749
17780
|
index("idx_channel_server_last_message").on(t.serverId, t.lastMessageAt),
|
|
17750
17781
|
index("idx_channel_parent").on(t.parentChannelId),
|
|
17782
|
+
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
17783
|
uniqueIndex("idx_channel_server_name").on(t.serverId, t.name).where(sql`parent_channel_id IS NULL`)
|
|
17752
17784
|
]);
|
|
17753
17785
|
var communityChannelMember = sqliteTable("community_channel_member", {
|
|
@@ -18769,50 +18801,6 @@ function createProxyServerApi(config2) {
|
|
|
18769
18801
|
});
|
|
18770
18802
|
return parseJsonResponse(res, "send");
|
|
18771
18803
|
}
|
|
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
18804
|
async function callRead(req) {
|
|
18817
18805
|
const q = new URLSearchParams;
|
|
18818
18806
|
q.set("ref", req.channel);
|
|
@@ -19096,7 +19084,6 @@ function createProxyServerApi(config2) {
|
|
|
19096
19084
|
inboxSnapshot: (_r) => callInboxSnapshot(),
|
|
19097
19085
|
ack: callAck,
|
|
19098
19086
|
send: callSend,
|
|
19099
|
-
createPost: callCreatePost,
|
|
19100
19087
|
read: callRead,
|
|
19101
19088
|
resolve: callResolve,
|
|
19102
19089
|
listMembers: callListMembers,
|
|
@@ -19162,7 +19149,7 @@ function cliCommandsSection() {
|
|
|
19162
19149
|
"### Messaging",
|
|
19163
19150
|
"",
|
|
19164
19151
|
`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.
|
|
19152
|
+
`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
19153
|
`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
19154
|
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
19168
19155
|
`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 +19180,7 @@ function cliCommandsSection() {
|
|
|
19193
19180
|
"",
|
|
19194
19181
|
"### Context Lifecycle",
|
|
19195
19182
|
"",
|
|
19196
|
-
`1. \`${CLI} nap --handoff <file>\`
|
|
19183
|
+
`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
19184
|
"",
|
|
19198
19185
|
"### Output format",
|
|
19199
19186
|
"",
|
|
@@ -19212,9 +19199,11 @@ function messagingSection() {
|
|
|
19212
19199
|
"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
19200
|
"",
|
|
19214
19201
|
"- 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
|
-
`-
|
|
19202
|
+
"Free-form message bodies never go in command arguments.",
|
|
19203
|
+
"",
|
|
19204
|
+
`- 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.",
|
|
19205
|
+
`- Long or complicated: write the body to a temporary file with a filesystem tool, then ` + `\`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
|
|
19206
|
+
`- Cite a specific message: add \`--reply "#37"\` to either form — \`--reply\` takes the \`#N\` seq ` + "(within `--target`) of the message you're answering.",
|
|
19218
19207
|
"",
|
|
19219
19208
|
"### Context refs",
|
|
19220
19209
|
"",
|
|
@@ -19254,8 +19243,10 @@ function messagingSection() {
|
|
|
19254
19243
|
"- **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
19244
|
"",
|
|
19256
19245
|
"```bash",
|
|
19257
|
-
"#
|
|
19258
|
-
`${CLI} message send --target "/demo#1234/general" --
|
|
19246
|
+
"# Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
|
|
19247
|
+
`${CLI} message send --target "/demo#1234/general" --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
19248
|
+
"@alice#0001 Please review /demo#1234/general#42",
|
|
19249
|
+
"ALOOK_MESSAGE_7F3C",
|
|
19259
19250
|
"```",
|
|
19260
19251
|
"",
|
|
19261
19252
|
"### Pulled messages",
|
|
@@ -19321,7 +19312,7 @@ function utilsSection() {
|
|
|
19321
19312
|
"",
|
|
19322
19313
|
`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
19314
|
"",
|
|
19324
|
-
`Example:
|
|
19315
|
+
`Example: ${CLI} message send --target "/demo#1234/team" --remind-after 1m --file ./message.md`
|
|
19325
19316
|
].join(`
|
|
19326
19317
|
`);
|
|
19327
19318
|
}
|
|
@@ -24031,7 +24022,7 @@ var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. C
|
|
|
24031
24022
|
function buildNapRewakePrompt(handoff) {
|
|
24032
24023
|
return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
|
|
24033
24024
|
|
|
24034
|
-
` + handoff
|
|
24025
|
+
` + handoff + `
|
|
24035
24026
|
|
|
24036
24027
|
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
24028
|
}
|
|
@@ -25611,7 +25602,10 @@ async function createDaemon(opts) {
|
|
|
25611
25602
|
let statusTimer = null;
|
|
25612
25603
|
if (opts.statusFilePath) {
|
|
25613
25604
|
const statusPath = opts.statusFilePath;
|
|
25614
|
-
const writeStatus = () =>
|
|
25605
|
+
const writeStatus = () => {
|
|
25606
|
+
const nowMs = Date.now();
|
|
25607
|
+
writeStatusFile(statusPath, { writtenAt: nowMs, agents: manager.statusProjection(nowMs) });
|
|
25608
|
+
};
|
|
25615
25609
|
writeStatus();
|
|
25616
25610
|
statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
|
|
25617
25611
|
statusTimer.unref?.();
|
|
@@ -27856,6 +27850,22 @@ function reconcileLegacyMachineKeyOwnership(baseDir, machineKey) {
|
|
|
27856
27850
|
removePidFileIfMatches(candidate, current);
|
|
27857
27851
|
}
|
|
27858
27852
|
}
|
|
27853
|
+
function daemonLastActiveMs(snapshot, nowMs) {
|
|
27854
|
+
const writtenAt = snapshot.writtenAt;
|
|
27855
|
+
if (writtenAt == null || !Number.isFinite(writtenAt) || writtenAt < 0)
|
|
27856
|
+
return null;
|
|
27857
|
+
let latest = null;
|
|
27858
|
+
for (const agent2 of snapshot.agents) {
|
|
27859
|
+
const sinceProgressMs = agent2.sinceProgressMs;
|
|
27860
|
+
if (!Number.isFinite(sinceProgressMs) || sinceProgressMs < 0)
|
|
27861
|
+
continue;
|
|
27862
|
+
const progressAt = writtenAt - sinceProgressMs;
|
|
27863
|
+
if (progressAt <= 0 || progressAt > nowMs)
|
|
27864
|
+
continue;
|
|
27865
|
+
latest = latest == null ? progressAt : Math.max(latest, progressAt);
|
|
27866
|
+
}
|
|
27867
|
+
return latest;
|
|
27868
|
+
}
|
|
27859
27869
|
function daemonList(opts) {
|
|
27860
27870
|
const baseDir = opts.baseDir || process.env.ALOOK_DATA_DIR || DEFAULT_BASE_DIR;
|
|
27861
27871
|
const dir = daemonsDir(baseDir);
|
|
@@ -27879,7 +27889,7 @@ function daemonList(opts) {
|
|
|
27879
27889
|
if (s.found) {
|
|
27880
27890
|
agents = s.agents.length;
|
|
27881
27891
|
running = s.agents.filter((a) => a.derivedActivity === "running").length;
|
|
27882
|
-
lastActiveMs = s
|
|
27892
|
+
lastActiveMs = daemonLastActiveMs(s, now);
|
|
27883
27893
|
}
|
|
27884
27894
|
}
|
|
27885
27895
|
results.push({ id, pid: data.pid, alive, agents, running, lastActiveMs });
|
|
@@ -28502,7 +28512,38 @@ var TEXT_ESCAPE_MAP = { n: `
|
|
|
28502
28512
|
function decodeTextEscapes(s) {
|
|
28503
28513
|
return s.replace(/\\(.)/g, (m, c) => TEXT_ESCAPE_MAP[c] ?? m);
|
|
28504
28514
|
}
|
|
28505
|
-
|
|
28515
|
+
async function readLiteralInput(args) {
|
|
28516
|
+
const { command, stdinSelected, stdin, filePath, fileOption } = args;
|
|
28517
|
+
if (stdinSelected && filePath !== undefined) {
|
|
28518
|
+
throw new CliError(`${command}: --stdin and ${fileOption} are mutually exclusive`);
|
|
28519
|
+
}
|
|
28520
|
+
if (stdinSelected) {
|
|
28521
|
+
if (!stdin)
|
|
28522
|
+
throw new CliError(`${command}: stdin is unavailable`);
|
|
28523
|
+
if (stdin.isTTY === true) {
|
|
28524
|
+
throw new CliError(`${command}: --stdin requires piped input; use ${fileOption} in an interactive terminal`);
|
|
28525
|
+
}
|
|
28526
|
+
try {
|
|
28527
|
+
const chunks = [];
|
|
28528
|
+
for await (const chunk2 of stdin) {
|
|
28529
|
+
chunks.push(typeof chunk2 === "string" ? Buffer.from(chunk2, "utf8") : Buffer.from(chunk2));
|
|
28530
|
+
}
|
|
28531
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
28532
|
+
} catch (err) {
|
|
28533
|
+
throw new CliError(`${command}: cannot read stdin: ${err.message}`);
|
|
28534
|
+
}
|
|
28535
|
+
}
|
|
28536
|
+
if (filePath !== undefined) {
|
|
28537
|
+
const fs13 = await import("fs/promises");
|
|
28538
|
+
try {
|
|
28539
|
+
return await fs13.readFile(filePath, "utf8");
|
|
28540
|
+
} catch (err) {
|
|
28541
|
+
throw new CliError(`${command}: cannot read file: ${err.message}`);
|
|
28542
|
+
}
|
|
28543
|
+
}
|
|
28544
|
+
return;
|
|
28545
|
+
}
|
|
28546
|
+
var CLIENT_MAX_ATTACHMENT_BYTES = 26214400;
|
|
28506
28547
|
function contentTypeFromFilename(filename) {
|
|
28507
28548
|
const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
|
|
28508
28549
|
switch (ext) {
|
|
@@ -28560,7 +28601,7 @@ async function withTransientMutationRetry(mutation) {
|
|
|
28560
28601
|
async function sendWithRetry(api2, req) {
|
|
28561
28602
|
return withTransientMutationRetry(() => api2.send(req));
|
|
28562
28603
|
}
|
|
28563
|
-
async function cmdMessageSend(opts) {
|
|
28604
|
+
async function cmdMessageSend(opts, stdin) {
|
|
28564
28605
|
const remindAfterFlag = opts.remindAfter;
|
|
28565
28606
|
const remindAfterMs = remindAfterFlag === undefined ? undefined : parseRemindAfter(remindAfterFlag);
|
|
28566
28607
|
const api2 = getApi();
|
|
@@ -28568,21 +28609,18 @@ async function cmdMessageSend(opts) {
|
|
|
28568
28609
|
const channel2 = opts.target;
|
|
28569
28610
|
if (!channel2)
|
|
28570
28611
|
throw new CliError("message send: --target <ref> is required (e.g. /demo-workspace#1234/general)");
|
|
28571
|
-
let text2;
|
|
28572
28612
|
const fileFlag = opts.file;
|
|
28573
|
-
const
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
|
|
28579
|
-
}
|
|
28580
|
-
text2 = decodeTextEscapes(textFlag);
|
|
28581
|
-
}
|
|
28613
|
+
const text2 = await readLiteralInput({
|
|
28614
|
+
command: "message send",
|
|
28615
|
+
stdinSelected: opts.stdin === true,
|
|
28616
|
+
stdin,
|
|
28617
|
+
filePath: fileFlag,
|
|
28618
|
+
fileOption: "--file <path>"
|
|
28619
|
+
});
|
|
28582
28620
|
const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
|
|
28583
28621
|
const hasText = typeof text2 === "string" && text2.trim().length > 0;
|
|
28584
28622
|
if (!hasText && attachmentIds.length === 0) {
|
|
28585
|
-
throw new CliError("message send: --
|
|
28623
|
+
throw new CliError("message send: --stdin, --file <path>, or --attachment <id> is required");
|
|
28586
28624
|
}
|
|
28587
28625
|
let replyToSeq;
|
|
28588
28626
|
const replyFlag = opts.reply;
|
|
@@ -28626,45 +28664,6 @@ async function cmdMessageSend(opts) {
|
|
|
28626
28664
|
return { sent, reminder: { armed: false, reason: "local reminder request failed" } };
|
|
28627
28665
|
}
|
|
28628
28666
|
}
|
|
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
28667
|
async function cmdMessageEmoji(opts) {
|
|
28669
28668
|
const api2 = getApi();
|
|
28670
28669
|
const target = opts.target;
|
|
@@ -28846,6 +28845,7 @@ async function cmdInboxPull(opts) {
|
|
|
28846
28845
|
const pulledAt = nowLocalISO();
|
|
28847
28846
|
let acked = 0;
|
|
28848
28847
|
let ackError;
|
|
28848
|
+
let failed;
|
|
28849
28849
|
if (opts.ack !== false && messages.length > 0) {
|
|
28850
28850
|
const latest = new Map;
|
|
28851
28851
|
for (const m of messages) {
|
|
@@ -28855,8 +28855,9 @@ async function cmdInboxPull(opts) {
|
|
|
28855
28855
|
latest.set(m.channel, { channel: m.channel, seq: seqN });
|
|
28856
28856
|
}
|
|
28857
28857
|
try {
|
|
28858
|
-
await api2.ack({ agentId: agent2, cursors: [...latest.values()] });
|
|
28859
|
-
acked =
|
|
28858
|
+
const result = await api2.ack({ agentId: agent2, cursors: [...latest.values()] });
|
|
28859
|
+
acked = result.applied.length;
|
|
28860
|
+
failed = result.failed;
|
|
28860
28861
|
} catch (err) {
|
|
28861
28862
|
ackError = err instanceof Error ? err.message : String(err);
|
|
28862
28863
|
}
|
|
@@ -28866,6 +28867,7 @@ async function cmdInboxPull(opts) {
|
|
|
28866
28867
|
hasMore,
|
|
28867
28868
|
acked,
|
|
28868
28869
|
pulledAt,
|
|
28870
|
+
...failed ? { failed } : {},
|
|
28869
28871
|
...ackError ? { ackError } : {},
|
|
28870
28872
|
...markedCount > 0 ? {
|
|
28871
28873
|
markedReminder: `You have ${markedCount} marked ${markedCount === 1 ? "message" : "messages"}. Resolve ${markedCount === 1 ? "it" : "them"} before going dark unless blocked.`
|
|
@@ -28961,38 +28963,28 @@ async function cmdFriendList(opts) {
|
|
|
28961
28963
|
async function cmdNap(opts) {
|
|
28962
28964
|
const api2 = getApi();
|
|
28963
28965
|
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>");
|
|
28966
|
+
const handoff = await readLiteralInput({
|
|
28967
|
+
command: "nap",
|
|
28968
|
+
stdinSelected: false,
|
|
28969
|
+
filePath: fileFlag,
|
|
28970
|
+
fileOption: "--handoff <file>"
|
|
28971
|
+
});
|
|
28972
|
+
if (handoff === undefined || handoff.trim().length === 0) {
|
|
28973
|
+
throw new CliError("nap: a handoff is required — pass --handoff <file>");
|
|
28976
28974
|
}
|
|
28977
28975
|
return await api2.nap({ handoff });
|
|
28978
28976
|
}
|
|
28979
|
-
function buildProgram() {
|
|
28977
|
+
function buildProgram(stdin) {
|
|
28980
28978
|
const program = new Command("alook").description("agent CLI").exitOverride().configureOutput({
|
|
28981
28979
|
writeOut: () => {},
|
|
28982
28980
|
writeErr: () => {}
|
|
28983
28981
|
}).option("--agent <id>", "agent identity (or ALOOK_AGENT_ID env)");
|
|
28984
28982
|
const message2 = program.command("message").description("message operations").exitOverride();
|
|
28985
28983
|
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() {
|
|
28984
|
+
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
28985
|
const localOpts = this.opts();
|
|
28994
28986
|
const globalOpts = program.opts();
|
|
28995
|
-
const result = await
|
|
28987
|
+
const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
|
|
28996
28988
|
printEnvelope({ success: result });
|
|
28997
28989
|
});
|
|
28998
28990
|
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 +29089,7 @@ function buildProgram() {
|
|
|
29097
29089
|
const result = await cmdSettingProfile({ ...program.opts(), ...this.opts() });
|
|
29098
29090
|
printEnvelope({ success: result });
|
|
29099
29091
|
});
|
|
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)").
|
|
29092
|
+
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
29093
|
const localOpts = this.opts();
|
|
29102
29094
|
const globalOpts = program.opts();
|
|
29103
29095
|
const result = await cmdNap({ ...globalOpts, ...localOpts });
|
|
@@ -29178,8 +29170,9 @@ function buildProgram() {
|
|
|
29178
29170
|
});
|
|
29179
29171
|
return program;
|
|
29180
29172
|
}
|
|
29181
|
-
async function main(argv = process.argv.slice(2)) {
|
|
29182
|
-
const
|
|
29173
|
+
async function main(argv = process.argv.slice(2), io = {}) {
|
|
29174
|
+
const stdin = io.stdin ?? process.stdin;
|
|
29175
|
+
const program = buildProgram(stdin);
|
|
29183
29176
|
let internalExitCode = 0;
|
|
29184
29177
|
try {
|
|
29185
29178
|
await program.parseAsync(argv, { from: "user" });
|
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ function cliCommandsSection() {
|
|
|
57
57
|
"### Messaging",
|
|
58
58
|
"",
|
|
59
59
|
`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).`,
|
|
60
|
-
`2. \`${CLI} message send\` — send to a channel, DM, or thread.
|
|
60
|
+
`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\`.`,
|
|
61
61
|
`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>\`.`,
|
|
62
62
|
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
63
63
|
`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\`).`,
|
|
@@ -88,7 +88,7 @@ function cliCommandsSection() {
|
|
|
88
88
|
"",
|
|
89
89
|
"### Context Lifecycle",
|
|
90
90
|
"",
|
|
91
|
-
`1. \`${CLI} nap --handoff <file>\`
|
|
91
|
+
`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.`,
|
|
92
92
|
"",
|
|
93
93
|
"### Output format",
|
|
94
94
|
"",
|
|
@@ -107,9 +107,11 @@ function messagingSection() {
|
|
|
107
107
|
"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.",
|
|
108
108
|
"",
|
|
109
109
|
"- 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.",
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`-
|
|
110
|
+
"Free-form message bodies never go in command arguments.",
|
|
111
|
+
"",
|
|
112
|
+
`- 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.",
|
|
113
|
+
`- Long or complicated: write the body to a temporary file with a filesystem tool, then ` + `\`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
|
|
114
|
+
`- Cite a specific message: add \`--reply "#37"\` to either form — \`--reply\` takes the \`#N\` seq ` + "(within `--target`) of the message you're answering.",
|
|
113
115
|
"",
|
|
114
116
|
"### Context refs",
|
|
115
117
|
"",
|
|
@@ -149,8 +151,10 @@ function messagingSection() {
|
|
|
149
151
|
"- **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.`,
|
|
150
152
|
"",
|
|
151
153
|
"```bash",
|
|
152
|
-
"#
|
|
153
|
-
`${CLI} message send --target "/demo#1234/general" --
|
|
154
|
+
"# Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
|
|
155
|
+
`${CLI} message send --target "/demo#1234/general" --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
156
|
+
"@alice#0001 Please review /demo#1234/general#42",
|
|
157
|
+
"ALOOK_MESSAGE_7F3C",
|
|
154
158
|
"```",
|
|
155
159
|
"",
|
|
156
160
|
"### Pulled messages",
|
|
@@ -216,7 +220,7 @@ function utilsSection() {
|
|
|
216
220
|
"",
|
|
217
221
|
`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.",
|
|
218
222
|
"",
|
|
219
|
-
`Example:
|
|
223
|
+
`Example: ${CLI} message send --target "/demo#1234/team" --remind-after 1m --file ./message.md`
|
|
220
224
|
].join(`
|
|
221
225
|
`);
|
|
222
226
|
}
|
|
@@ -4863,7 +4867,7 @@ var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. C
|
|
|
4863
4867
|
function buildNapRewakePrompt(handoff) {
|
|
4864
4868
|
return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
|
|
4865
4869
|
|
|
4866
|
-
` + handoff
|
|
4870
|
+
` + handoff + `
|
|
4867
4871
|
|
|
4868
4872
|
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.";
|
|
4869
4873
|
}
|
|
@@ -24028,7 +24032,10 @@ async function createDaemon(opts) {
|
|
|
24028
24032
|
let statusTimer = null;
|
|
24029
24033
|
if (opts.statusFilePath) {
|
|
24030
24034
|
const statusPath = opts.statusFilePath;
|
|
24031
|
-
const writeStatus = () =>
|
|
24035
|
+
const writeStatus = () => {
|
|
24036
|
+
const nowMs = Date.now();
|
|
24037
|
+
writeStatusFile(statusPath, { writtenAt: nowMs, agents: manager.statusProjection(nowMs) });
|
|
24038
|
+
};
|
|
24032
24039
|
writeStatus();
|
|
24033
24040
|
statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
|
|
24034
24041
|
statusTimer.unref?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alook/daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/alookai/alook#readme",
|