crewly 1.20.347 → 1.20.349

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/config/roles/orchestrator/prompt.md +1 -1
  2. package/config/roles/team-leader/prompt.md +1 -1
  3. package/config/skills/agent/core/publish-app/SKILL.md +10 -0
  4. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
  5. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +8 -0
  6. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
  7. package/dist/backend/backend/src/index.d.ts +15 -0
  8. package/dist/backend/backend/src/index.d.ts.map +1 -1
  9. package/dist/backend/backend/src/index.js +73 -1
  10. package/dist/backend/backend/src/index.js.map +1 -1
  11. package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.js +1 -1
  12. package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -1
  13. package/dist/backend/backend/src/services/apps/app-wake-message.d.ts +21 -0
  14. package/dist/backend/backend/src/services/apps/app-wake-message.d.ts.map +1 -1
  15. package/dist/backend/backend/src/services/apps/app-wake-message.js +33 -2
  16. package/dist/backend/backend/src/services/apps/app-wake-message.js.map +1 -1
  17. package/dist/backend/backend/src/services/apps/app-wake.service.d.ts.map +1 -1
  18. package/dist/backend/backend/src/services/apps/app-wake.service.js +15 -1
  19. package/dist/backend/backend/src/services/apps/app-wake.service.js.map +1 -1
  20. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.service.d.ts +165 -0
  21. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.service.d.ts.map +1 -0
  22. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.service.js +337 -0
  23. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.service.js.map +1 -0
  24. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.wiring.d.ts +45 -0
  25. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.wiring.d.ts.map +1 -0
  26. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.wiring.js +71 -0
  27. package/dist/backend/backend/src/services/messaging/agent-ask-ledger.wiring.js.map +1 -0
  28. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  29. package/dist/backend/backend/src/services/trace/trace-context.service.js +11 -0
  30. package/dist/backend/backend/src/services/trace/trace-context.service.js.map +1 -1
  31. package/dist/backend/backend/src/services/v3/ticket-review.service.d.ts +21 -0
  32. package/dist/backend/backend/src/services/v3/ticket-review.service.d.ts.map +1 -1
  33. package/dist/backend/backend/src/services/v3/ticket-review.service.js +35 -0
  34. package/dist/backend/backend/src/services/v3/ticket-review.service.js.map +1 -1
  35. package/dist/backend/build-info.json +2 -2
  36. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js +1 -1
  37. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -1
  38. package/dist/cli/backend/src/services/messaging/agent-ask-ledger.service.d.ts +165 -0
  39. package/dist/cli/backend/src/services/messaging/agent-ask-ledger.service.d.ts.map +1 -0
  40. package/dist/cli/backend/src/services/messaging/agent-ask-ledger.service.js +337 -0
  41. package/dist/cli/backend/src/services/messaging/agent-ask-ledger.service.js.map +1 -0
  42. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  43. package/dist/cli/backend/src/services/trace/trace-context.service.js +11 -0
  44. package/dist/cli/backend/src/services/trace/trace-context.service.js.map +1 -1
  45. package/dist/cli/backend/src/services/v3/ticket-review.service.d.ts +21 -0
  46. package/dist/cli/backend/src/services/v3/ticket-review.service.d.ts.map +1 -1
  47. package/dist/cli/backend/src/services/v3/ticket-review.service.js +35 -0
  48. package/dist/cli/backend/src/services/v3/ticket-review.service.js.map +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Agent ask ledger — the return path of agent-to-agent messages.
3
+ *
4
+ * When one agent hands work to another with a plain message (send-message /
5
+ * `/terminal/:to/write`), nothing brings the answer back: no WorkItem, no
6
+ * completion event. On 2026-10-14 Pia passed the owner's "replicate this X
7
+ * video" to Dex; Dex rendered the films in 24 minutes, wrote "both parts are
8
+ * done" into its own terminal and stopped. Nobody saw it for seven hours.
9
+ *
10
+ * This ledger remembers each agent message once it is delivered. When the
11
+ * receiver ends a turn that worked on it for a while and it neither wrote
12
+ * back to the sender nor posted anywhere, the harness passes the receiver's
13
+ * last words to the sender. The receiver's open asks also hold the owner's
14
+ * ticket open (an agent that handed the work on has not answered yet).
15
+ *
16
+ * Cheap by construction: no model call, one transcript read per forwarded
17
+ * ask. Off switch: `CREWLY_AGENT_ASK_RETURN=off`.
18
+ *
19
+ * @module services/messaging/agent-ask-ledger.service
20
+ */
21
+ /** Tunables. */
22
+ export declare const AGENT_ASK_CONSTANTS: {
23
+ /** Store file under CREWLY_HOME */
24
+ readonly FILE_NAME: "agent-asks.json";
25
+ /** A turn this long after the delivery counts as having worked on the message (ms) */
26
+ readonly MIN_WORK_MS: number;
27
+ /**
28
+ * A turn that ends sooner than MIN_WORK_MS but at least this long after
29
+ * the delivery read it and let it go (an FYI, an "ok"): the ask is dropped
30
+ * without a word. Sooner than this, the turn that ended is the previous
31
+ * one, not the message's (ms).
32
+ */
33
+ readonly MIN_READ_MS: 15000;
34
+ /** An ask nobody answered is forgotten after this (ms) */
35
+ readonly TTL_MS: number;
36
+ /** Asks kept in the store */
37
+ readonly MAX_ASKS: 300;
38
+ /** Characters of the asked message quoted back to the sender */
39
+ readonly EXCERPT_CHARS: 200;
40
+ /** Characters of the receiver's last words passed on */
41
+ readonly RESULT_CHARS: 3000;
42
+ /** Env switch: `off` disables the ledger */
43
+ readonly ENV_SWITCH: "CREWLY_AGENT_ASK_RETURN";
44
+ };
45
+ /** One delivered agent-to-agent message. */
46
+ export interface AgentAsk {
47
+ /** Sending agent */
48
+ sender: string;
49
+ /** Receiving agent */
50
+ target: string;
51
+ /** Start of the message */
52
+ excerpt: string;
53
+ /** When it reached the receiver's turn (epoch ms) */
54
+ deliveredAt: number;
55
+ }
56
+ /** Collaborators. */
57
+ export interface AgentAskLedgerDeps {
58
+ /** Store file; omit to keep the ledger in memory only */
59
+ file?: string;
60
+ /** What the receiver last wrote in its own transcript since `sinceMs`, or null */
61
+ readLastWords: (session: string, sinceMs: number) => Promise<string | null>;
62
+ /** Deliver a harness message to an agent */
63
+ deliver: (session: string, text: string) => Promise<unknown>;
64
+ /** Readable name of an agent (default: the member part of its session name) */
65
+ displayName?: (session: string) => string;
66
+ now?: () => number;
67
+ }
68
+ /**
69
+ * The member name in a session id: `flopost-dex-7f17ecbc` → `Dex`.
70
+ *
71
+ * @param session - Session name
72
+ * @returns Readable name
73
+ */
74
+ export declare function memberName(session: string): string;
75
+ /**
76
+ * Text handed to the sender in place of the answer it never got.
77
+ *
78
+ * @param input - Names, the asked excerpt, minutes worked, the last words
79
+ * @returns Message for the sender
80
+ */
81
+ export declare function renderReturnedAnswer(input: {
82
+ target: string;
83
+ excerpt: string;
84
+ minutes: number;
85
+ lastWords: string | null;
86
+ }): string;
87
+ /**
88
+ * Remembers delivered agent messages and returns unsent answers. See module docs.
89
+ */
90
+ export declare class AgentAskLedger {
91
+ private readonly deps;
92
+ private asks;
93
+ private posts;
94
+ private readonly logger;
95
+ private readonly now;
96
+ private saving;
97
+ constructor(deps: AgentAskLedgerDeps);
98
+ /**
99
+ * A message from `sender` reached `target`'s turn.
100
+ *
101
+ * @param target - Receiving agent
102
+ * @param sender - Sending agent
103
+ * @param text - What was delivered
104
+ */
105
+ noteDelivered(target: string, sender: string, text: string): void;
106
+ /**
107
+ * `from` wrote to `to`: every open ask of `to` to `from` is answered.
108
+ *
109
+ * @param from - Writing agent
110
+ * @param to - Agent written to
111
+ */
112
+ noteAnswered(from: string, to: string): void;
113
+ /**
114
+ * An agent posted in chat (an answer, not an interim note): asks delivered
115
+ * to it before now count as answered where they were asked to be.
116
+ *
117
+ * @param session - Posting agent
118
+ */
119
+ notePost(session: string): void;
120
+ /**
121
+ * Asks `sender` made that are still unanswered.
122
+ *
123
+ * @param sender - Asking agent
124
+ * @returns Open asks, oldest first
125
+ */
126
+ openAsksBy(sender: string): AgentAsk[];
127
+ /**
128
+ * `target` finished a turn. An ask it worked on for at least
129
+ * {@link AGENT_ASK_CONSTANTS.MIN_WORK_MS} with no word back is answered
130
+ * for it: its last words go to the sender. One it answered in chat, or
131
+ * read in a short turn, is dropped. Never throws.
132
+ *
133
+ * @param target - Agent whose turn ended
134
+ * @returns Asks returned to their senders
135
+ */
136
+ onTurnEnded(target: string): Promise<number>;
137
+ private prune;
138
+ private load;
139
+ private save;
140
+ /** Wait for pending writes (tests). */
141
+ flush(): Promise<void>;
142
+ }
143
+ /**
144
+ * The agent's last message in a transcript since a time: the text of the
145
+ * newest assistant entry that has text (Claude Code JSONL or Codex rollout).
146
+ * Subagent (sidechain), meta and API-error entries are not the agent's words.
147
+ *
148
+ * @param text - Whole JSONL lines (a tail of the file is fine)
149
+ * @param kind - Transcript format
150
+ * @param sinceMs - Ignore entries older than this (epoch ms)
151
+ * @returns The text, or null when there is none
152
+ */
153
+ export declare function lastWordsFromTranscript(text: string, kind: 'claude' | 'codex', sinceMs: number): string | null;
154
+ /** @returns The wired ledger, or null before boot (and when switched off) */
155
+ export declare function getAgentAskLedger(): AgentAskLedger | null;
156
+ /** @param ledger - The ledger to expose (null clears) */
157
+ export declare function setAgentAskLedger(ledger: AgentAskLedger | null): void;
158
+ /**
159
+ * Whether the ledger is switched on.
160
+ *
161
+ * @param env - Environment
162
+ * @returns False when `CREWLY_AGENT_ASK_RETURN=off`
163
+ */
164
+ export declare function agentAskReturnEnabled(env?: NodeJS.ProcessEnv): boolean;
165
+ //# sourceMappingURL=agent-ask-ledger.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-ask-ledger.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/agent-ask-ledger.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Agent ask ledger — the return path of agent-to-agent messages.
3
+ *
4
+ * When one agent hands work to another with a plain message (send-message /
5
+ * `/terminal/:to/write`), nothing brings the answer back: no WorkItem, no
6
+ * completion event. On 2026-10-14 Pia passed the owner's "replicate this X
7
+ * video" to Dex; Dex rendered the films in 24 minutes, wrote "both parts are
8
+ * done" into its own terminal and stopped. Nobody saw it for seven hours.
9
+ *
10
+ * This ledger remembers each agent message once it is delivered. When the
11
+ * receiver ends a turn that worked on it for a while and it neither wrote
12
+ * back to the sender nor posted anywhere, the harness passes the receiver's
13
+ * last words to the sender. The receiver's open asks also hold the owner's
14
+ * ticket open (an agent that handed the work on has not answered yet).
15
+ *
16
+ * Cheap by construction: no model call, one transcript read per forwarded
17
+ * ask. Off switch: `CREWLY_AGENT_ASK_RETURN=off`.
18
+ *
19
+ * @module services/messaging/agent-ask-ledger.service
20
+ */
21
+ import * as fsSync from 'fs';
22
+ import * as path from 'path';
23
+ import { LoggerService } from '../core/logger.service.js';
24
+ import { atomicWriteJson } from '../../utils/file-io.utils.js';
25
+ /** Tunables. */
26
+ export const AGENT_ASK_CONSTANTS = {
27
+ /** Store file under CREWLY_HOME */
28
+ FILE_NAME: 'agent-asks.json',
29
+ /** A turn this long after the delivery counts as having worked on the message (ms) */
30
+ MIN_WORK_MS: 2 * 60_000,
31
+ /**
32
+ * A turn that ends sooner than MIN_WORK_MS but at least this long after
33
+ * the delivery read it and let it go (an FYI, an "ok"): the ask is dropped
34
+ * without a word. Sooner than this, the turn that ended is the previous
35
+ * one, not the message's (ms).
36
+ */
37
+ MIN_READ_MS: 15_000,
38
+ /** An ask nobody answered is forgotten after this (ms) */
39
+ TTL_MS: 12 * 60 * 60_000,
40
+ /** Asks kept in the store */
41
+ MAX_ASKS: 300,
42
+ /** Characters of the asked message quoted back to the sender */
43
+ EXCERPT_CHARS: 200,
44
+ /** Characters of the receiver's last words passed on */
45
+ RESULT_CHARS: 3000,
46
+ /** Env switch: `off` disables the ledger */
47
+ ENV_SWITCH: 'CREWLY_AGENT_ASK_RETURN',
48
+ };
49
+ const C = AGENT_ASK_CONSTANTS;
50
+ /**
51
+ * The member name in a session id: `flopost-dex-7f17ecbc` → `Dex`.
52
+ *
53
+ * @param session - Session name
54
+ * @returns Readable name
55
+ */
56
+ export function memberName(session) {
57
+ const parts = session.split('-');
58
+ const name = parts.length >= 3 && /^[0-9a-f]{6,}$/.test(parts[parts.length - 1]) ? parts[parts.length - 2] : session;
59
+ return name.charAt(0).toUpperCase() + name.slice(1);
60
+ }
61
+ /**
62
+ * Text handed to the sender in place of the answer it never got.
63
+ *
64
+ * @param input - Names, the asked excerpt, minutes worked, the last words
65
+ * @returns Message for the sender
66
+ */
67
+ export function renderReturnedAnswer(input) {
68
+ const head = `[ANSWER FROM ${input.target.toUpperCase()}] ${input.target} worked on your message for about ${input.minutes} min and ended without writing back to you or posting anywhere.`;
69
+ const asked = `Your message: "${input.excerpt}"`;
70
+ if (!input.lastWords) {
71
+ return `${head} Crewly could not read what it said. ${asked}\nAsk ${input.target} for the result.`;
72
+ }
73
+ return (`${head} Crewly is passing on its last words. ${asked}\n\n` +
74
+ `--- ${input.target} ---\n${input.lastWords}\n---\n\n` +
75
+ `If this is what you were waiting for, carry it on (for example, post it where the owner asked). No need to answer Crewly.`);
76
+ }
77
+ /**
78
+ * Remembers delivered agent messages and returns unsent answers. See module docs.
79
+ */
80
+ export class AgentAskLedger {
81
+ deps;
82
+ asks = [];
83
+ posts = new Map();
84
+ logger;
85
+ now;
86
+ saving = Promise.resolve();
87
+ constructor(deps) {
88
+ this.deps = deps;
89
+ this.logger = LoggerService.getInstance().createComponentLogger('AgentAskLedger');
90
+ this.now = deps.now ?? Date.now;
91
+ this.load();
92
+ }
93
+ /**
94
+ * A message from `sender` reached `target`'s turn.
95
+ *
96
+ * @param target - Receiving agent
97
+ * @param sender - Sending agent
98
+ * @param text - What was delivered
99
+ */
100
+ noteDelivered(target, sender, text) {
101
+ if (!target || !sender || target === sender)
102
+ return;
103
+ const excerpt = text.replace(/\s+/g, ' ').trim().slice(0, C.EXCERPT_CHARS);
104
+ this.asks.push({ sender, target, excerpt, deliveredAt: this.now() });
105
+ this.prune();
106
+ this.save();
107
+ }
108
+ /**
109
+ * `from` wrote to `to`: every open ask of `to` to `from` is answered.
110
+ *
111
+ * @param from - Writing agent
112
+ * @param to - Agent written to
113
+ */
114
+ noteAnswered(from, to) {
115
+ const before = this.asks.length;
116
+ this.asks = this.asks.filter((a) => !(a.target === from && a.sender === to));
117
+ if (this.asks.length !== before)
118
+ this.save();
119
+ }
120
+ /**
121
+ * An agent posted in chat (an answer, not an interim note): asks delivered
122
+ * to it before now count as answered where they were asked to be.
123
+ *
124
+ * @param session - Posting agent
125
+ */
126
+ notePost(session) {
127
+ if (!session)
128
+ return;
129
+ this.posts.set(session, this.now());
130
+ this.save();
131
+ }
132
+ /**
133
+ * Asks `sender` made that are still unanswered.
134
+ *
135
+ * @param sender - Asking agent
136
+ * @returns Open asks, oldest first
137
+ */
138
+ openAsksBy(sender) {
139
+ this.prune();
140
+ return this.asks.filter((a) => a.sender === sender);
141
+ }
142
+ /**
143
+ * `target` finished a turn. An ask it worked on for at least
144
+ * {@link AGENT_ASK_CONSTANTS.MIN_WORK_MS} with no word back is answered
145
+ * for it: its last words go to the sender. One it answered in chat, or
146
+ * read in a short turn, is dropped. Never throws.
147
+ *
148
+ * @param target - Agent whose turn ended
149
+ * @returns Asks returned to their senders
150
+ */
151
+ async onTurnEnded(target) {
152
+ try {
153
+ this.prune();
154
+ const now = this.now();
155
+ const mine = this.asks.filter((a) => a.target === target);
156
+ if (mine.length === 0)
157
+ return 0;
158
+ const posted = this.posts.get(target) ?? 0;
159
+ const age = (a) => now - a.deliveredAt;
160
+ // Answered in chat, or read and let go after a short turn: drop quietly.
161
+ const quiet = mine.filter((a) => posted >= a.deliveredAt || (age(a) >= C.MIN_READ_MS && age(a) < C.MIN_WORK_MS));
162
+ const due = mine.filter((a) => posted < a.deliveredAt && age(a) >= C.MIN_WORK_MS);
163
+ if (quiet.length === 0 && due.length === 0)
164
+ return 0;
165
+ const settled = new Set([...quiet, ...due]);
166
+ this.asks = this.asks.filter((a) => !settled.has(a));
167
+ this.save();
168
+ // One message per sender, from the oldest ask it is waiting on.
169
+ const bySender = new Map();
170
+ for (const a of due)
171
+ if (!bySender.has(a.sender))
172
+ bySender.set(a.sender, a);
173
+ let returned = 0;
174
+ for (const ask of bySender.values()) {
175
+ const lastWords = await this.deps.readLastWords(target, ask.deliveredAt).catch(() => null);
176
+ const text = renderReturnedAnswer({
177
+ target: (this.deps.displayName ?? memberName)(target),
178
+ excerpt: ask.excerpt,
179
+ minutes: Math.max(1, Math.round((now - ask.deliveredAt) / 60_000)),
180
+ lastWords: lastWords ? clipResult(lastWords) : null,
181
+ });
182
+ try {
183
+ await this.deps.deliver(ask.sender, text);
184
+ returned += 1;
185
+ this.logger.info('Returned an unsent answer to its asker', { from: target, to: ask.sender, hadText: !!lastWords });
186
+ }
187
+ catch (err) {
188
+ this.logger.warn('Could not return an unsent answer', { from: target, to: ask.sender, error: err instanceof Error ? err.message : String(err) });
189
+ }
190
+ }
191
+ return returned;
192
+ }
193
+ catch (err) {
194
+ this.logger.debug('Ask settle failed', { target, error: err instanceof Error ? err.message : String(err) });
195
+ return 0;
196
+ }
197
+ }
198
+ prune() {
199
+ const cutoff = this.now() - C.TTL_MS;
200
+ this.asks = this.asks.filter((a) => a.deliveredAt >= cutoff).slice(-C.MAX_ASKS);
201
+ for (const [s, at] of this.posts)
202
+ if (at < cutoff)
203
+ this.posts.delete(s);
204
+ }
205
+ load() {
206
+ if (!this.deps.file)
207
+ return;
208
+ try {
209
+ const raw = JSON.parse(fsSync.readFileSync(this.deps.file, 'utf-8'));
210
+ if (Array.isArray(raw.asks)) {
211
+ this.asks = raw.asks.filter((a) => !!a && typeof a.sender === 'string' && typeof a.target === 'string' && typeof a.excerpt === 'string' && typeof a.deliveredAt === 'number');
212
+ }
213
+ if (raw.posts && typeof raw.posts === 'object') {
214
+ for (const [s, at] of Object.entries(raw.posts))
215
+ if (typeof at === 'number')
216
+ this.posts.set(s, at);
217
+ }
218
+ this.prune();
219
+ }
220
+ catch {
221
+ // missing or unreadable: start empty
222
+ }
223
+ }
224
+ save() {
225
+ const file = this.deps.file;
226
+ if (!file)
227
+ return;
228
+ const data = { asks: this.asks, posts: Object.fromEntries(this.posts) };
229
+ this.saving = this.saving
230
+ .then(async () => {
231
+ fsSync.mkdirSync(path.dirname(file), { recursive: true });
232
+ await atomicWriteJson(file, data);
233
+ })
234
+ .catch((err) => {
235
+ this.logger.debug('Ask ledger not saved', { error: err instanceof Error ? err.message : String(err) });
236
+ });
237
+ }
238
+ /** Wait for pending writes (tests). */
239
+ async flush() {
240
+ await this.saving;
241
+ }
242
+ }
243
+ /**
244
+ * The agent's last message in a transcript since a time: the text of the
245
+ * newest assistant entry that has text (Claude Code JSONL or Codex rollout).
246
+ * Subagent (sidechain), meta and API-error entries are not the agent's words.
247
+ *
248
+ * @param text - Whole JSONL lines (a tail of the file is fine)
249
+ * @param kind - Transcript format
250
+ * @param sinceMs - Ignore entries older than this (epoch ms)
251
+ * @returns The text, or null when there is none
252
+ */
253
+ export function lastWordsFromTranscript(text, kind, sinceMs) {
254
+ let last = null;
255
+ for (const line of text.split('\n')) {
256
+ if (line.length < 20)
257
+ continue;
258
+ if (kind === 'claude' && !(line.includes('"type":"assistant"') && line.includes('"type":"text"')))
259
+ continue;
260
+ if (kind === 'codex' && !line.includes('"agent_message"') && !line.includes('"output_text"'))
261
+ continue;
262
+ let entry;
263
+ try {
264
+ entry = JSON.parse(line);
265
+ }
266
+ catch {
267
+ continue;
268
+ }
269
+ const at = typeof entry.timestamp === 'string' ? Date.parse(entry.timestamp) : NaN;
270
+ if (!Number.isFinite(at) || at < sinceMs)
271
+ continue;
272
+ let found = null;
273
+ if (kind === 'claude') {
274
+ if (entry.type !== 'assistant' || entry.isSidechain === true || entry.isMeta === true || entry.isApiErrorMessage === true)
275
+ continue;
276
+ const content = entry.message?.content;
277
+ if (!Array.isArray(content))
278
+ continue;
279
+ const parts = content
280
+ .filter((b) => !!b && b.type === 'text' && typeof b.text === 'string')
281
+ .map((b) => b.text.trim())
282
+ .filter(Boolean);
283
+ if (parts.length > 0)
284
+ found = parts.join('\n');
285
+ }
286
+ else {
287
+ const p = entry.payload;
288
+ if (!p)
289
+ continue;
290
+ if (entry.type === 'event_msg' && p.type === 'agent_message' && typeof p.message === 'string')
291
+ found = p.message.trim();
292
+ else if (entry.type === 'response_item' && p.type === 'message' && p.role === 'assistant' && Array.isArray(p.content)) {
293
+ const parts = p.content
294
+ .filter((b) => b?.type === 'output_text' && typeof b.text === 'string')
295
+ .map((b) => b.text.trim())
296
+ .filter(Boolean);
297
+ if (parts.length > 0)
298
+ found = parts.join('\n');
299
+ }
300
+ }
301
+ if (found)
302
+ last = found;
303
+ }
304
+ return last;
305
+ }
306
+ /**
307
+ * Keep the end of a long text: the conclusion is at the end.
308
+ *
309
+ * @param text - Last words
310
+ * @returns At most {@link AGENT_ASK_CONSTANTS.RESULT_CHARS} characters
311
+ */
312
+ function clipResult(text) {
313
+ const t = text.trim();
314
+ return t.length <= C.RESULT_CHARS ? t : `…${t.slice(-(C.RESULT_CHARS - 1))}`;
315
+ }
316
+ // ---------------------------------------------------------------------------
317
+ // Singleton
318
+ // ---------------------------------------------------------------------------
319
+ let instance = null;
320
+ /** @returns The wired ledger, or null before boot (and when switched off) */
321
+ export function getAgentAskLedger() {
322
+ return instance;
323
+ }
324
+ /** @param ledger - The ledger to expose (null clears) */
325
+ export function setAgentAskLedger(ledger) {
326
+ instance = ledger;
327
+ }
328
+ /**
329
+ * Whether the ledger is switched on.
330
+ *
331
+ * @param env - Environment
332
+ * @returns False when `CREWLY_AGENT_ASK_RETURN=off`
333
+ */
334
+ export function agentAskReturnEnabled(env = process.env) {
335
+ return (env[C.ENV_SWITCH] ?? '').toLowerCase() !== 'off';
336
+ }
337
+ //# sourceMappingURL=agent-ask-ledger.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-ask-ledger.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/agent-ask-ledger.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Boot wiring of the agent ask ledger: the store file, the transcript reader
3
+ * and the delivery used to hand an unsent answer to its asker.
4
+ *
5
+ * @module services/messaging/agent-ask-ledger.wiring
6
+ */
7
+ import { AgentAskLedger } from './agent-ask-ledger.service.js';
8
+ /** Bytes read from the end of a transcript: a long turn's last message is near the end. */
9
+ export declare const LAST_WORDS_TAIL_BYTES: number;
10
+ /** Where a session's transcript is (see the canvas transcript locator). */
11
+ export type AskTranscriptLocator = (session: string) => Promise<{
12
+ kind: 'claude' | 'codex';
13
+ file: string;
14
+ } | null>;
15
+ /**
16
+ * Read the end of a file as whole lines (the first partial line is dropped).
17
+ *
18
+ * @param file - Path
19
+ * @param maxBytes - Bytes from the end
20
+ * @returns Text
21
+ */
22
+ export declare function readTail(file: string, maxBytes?: number): Promise<string>;
23
+ /**
24
+ * The last words of a session since a time, secrets masked.
25
+ *
26
+ * @param locate - Transcript locator
27
+ * @param session - Agent session
28
+ * @param sinceMs - Oldest entry to read (epoch ms)
29
+ * @returns Text, or null (no transcript, nothing said)
30
+ */
31
+ export declare function readLastWords(locate: AskTranscriptLocator, session: string, sinceMs: number): Promise<string | null>;
32
+ /**
33
+ * Create and expose the ledger. Returns null when switched off.
34
+ *
35
+ * @param deps.crewlyHome - CREWLY_HOME (store file location)
36
+ * @param deps.sendToAgent - Delivers a harness message to an agent
37
+ * @param deps.locate - Transcript locator
38
+ * @returns The ledger, or null
39
+ */
40
+ export declare function createAgentAskLedger(deps: {
41
+ crewlyHome: string;
42
+ sendToAgent: (session: string, text: string) => Promise<unknown>;
43
+ locate: AskTranscriptLocator;
44
+ }): AgentAskLedger | null;
45
+ //# sourceMappingURL=agent-ask-ledger.wiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-ask-ledger.wiring.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/agent-ask-ledger.wiring.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAEN,cAAc,EAId,MAAM,+BAA+B,CAAC;AAEvC,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,QAAkB,CAAC;AAErD,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC;AAEnH;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA8B,GAAG,OAAO,CAAC,MAAM,CAAC,CAYtG;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAK1H;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,MAAM,EAAE,oBAAoB,CAAC;CAC7B,GAAG,cAAc,GAAG,IAAI,CAYxB"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Boot wiring of the agent ask ledger: the store file, the transcript reader
3
+ * and the delivery used to hand an unsent answer to its asker.
4
+ *
5
+ * @module services/messaging/agent-ask-ledger.wiring
6
+ */
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import { collectSecretEnvValues } from '../../utils/secret-env.js';
10
+ import { redactSecrets } from '../../utils/secret-redactor.js';
11
+ import { AGENT_ASK_CONSTANTS, AgentAskLedger, agentAskReturnEnabled, lastWordsFromTranscript, setAgentAskLedger, } from './agent-ask-ledger.service.js';
12
+ /** Bytes read from the end of a transcript: a long turn's last message is near the end. */
13
+ export const LAST_WORDS_TAIL_BYTES = 2 * 1024 * 1024;
14
+ /**
15
+ * Read the end of a file as whole lines (the first partial line is dropped).
16
+ *
17
+ * @param file - Path
18
+ * @param maxBytes - Bytes from the end
19
+ * @returns Text
20
+ */
21
+ export async function readTail(file, maxBytes = LAST_WORDS_TAIL_BYTES) {
22
+ const handle = await fs.open(file, 'r');
23
+ try {
24
+ const { size } = await handle.stat();
25
+ const start = Math.max(0, size - maxBytes);
26
+ const buf = Buffer.alloc(size - start);
27
+ await handle.read(buf, 0, buf.length, start);
28
+ const text = buf.toString('utf-8');
29
+ return start > 0 ? text.slice(text.indexOf('\n') + 1) : text;
30
+ }
31
+ finally {
32
+ await handle.close();
33
+ }
34
+ }
35
+ /**
36
+ * The last words of a session since a time, secrets masked.
37
+ *
38
+ * @param locate - Transcript locator
39
+ * @param session - Agent session
40
+ * @param sinceMs - Oldest entry to read (epoch ms)
41
+ * @returns Text, or null (no transcript, nothing said)
42
+ */
43
+ export async function readLastWords(locate, session, sinceMs) {
44
+ const target = await locate(session);
45
+ if (!target)
46
+ return null;
47
+ const words = lastWordsFromTranscript(await readTail(target.file), target.kind, sinceMs);
48
+ return words ? redactSecrets(words, collectSecretEnvValues(process.env)) : null;
49
+ }
50
+ /**
51
+ * Create and expose the ledger. Returns null when switched off.
52
+ *
53
+ * @param deps.crewlyHome - CREWLY_HOME (store file location)
54
+ * @param deps.sendToAgent - Delivers a harness message to an agent
55
+ * @param deps.locate - Transcript locator
56
+ * @returns The ledger, or null
57
+ */
58
+ export function createAgentAskLedger(deps) {
59
+ if (!agentAskReturnEnabled()) {
60
+ setAgentAskLedger(null);
61
+ return null;
62
+ }
63
+ const ledger = new AgentAskLedger({
64
+ file: path.join(deps.crewlyHome, AGENT_ASK_CONSTANTS.FILE_NAME),
65
+ readLastWords: (session, sinceMs) => readLastWords(deps.locate, session, sinceMs),
66
+ deliver: deps.sendToAgent,
67
+ });
68
+ setAgentAskLedger(ledger);
69
+ return ledger;
70
+ }
71
+ //# sourceMappingURL=agent-ask-ledger.wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-ask-ledger.wiring.js","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/agent-ask-ledger.wiring.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EACN,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,GACjB,MAAM,+BAA+B,CAAC;AAEvC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAKrD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,WAAmB,qBAAqB;IACpF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC;QACJ,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;QACvC,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAA4B,EAAE,OAAe,EAAE,OAAe;IACjG,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,KAAK,GAAG,uBAAuB,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzF,OAAO,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAIpC;IACA,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC9B,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;QACjC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,mBAAmB,CAAC,SAAS,CAAC;QAC/D,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC;QACjF,OAAO,EAAE,IAAI,CAAC,WAAW;KACzB,CAAC,CAAC;IACH,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC;AACf,CAAC"}