@tekmidian/pai 0.66.1 → 0.66.2
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/{aibroker-client-B8c42Lh8.mjs → aibroker-client-CHEEvJZW.mjs} +2 -2
- package/dist/{aibroker-client-C5Fw7DNz.mjs → aibroker-client-Dfv7j1Od.mjs} +3 -3
- package/dist/{aibroker-client-C5Fw7DNz.mjs.map → aibroker-client-Dfv7j1Od.mjs.map} +1 -1
- package/dist/{auto-route-o0BOsXn7.mjs → auto-route-BnizyALK.mjs} +65 -4
- package/dist/auto-route-BnizyALK.mjs.map +1 -0
- package/dist/auto-route-D6fW1Q8z.mjs +3 -0
- package/dist/{chain-CZfKIEq9.mjs → chain-D5876UQX.mjs} +3 -4
- package/dist/{chain-CZfKIEq9.mjs.map → chain-D5876UQX.mjs.map} +1 -1
- package/dist/cli/index.mjs +18 -23
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +17 -22
- package/dist/{config-BbLFD7Uf.mjs → config-C-LiGlop.mjs} +2 -2
- package/dist/{config-BbLFD7Uf.mjs.map → config-C-LiGlop.mjs.map} +1 -1
- package/dist/{config-YinjgXEJ.mjs → config-DKIA7wgF.mjs} +1 -1
- package/dist/{context-handover-cache-pkHzmL3c.mjs → context-handover-cache-9PGvXRIw.mjs} +984 -10
- package/dist/context-handover-cache-9PGvXRIw.mjs.map +1 -0
- package/dist/daemon/index.mjs +17 -19
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BUgNvMUh.mjs +5029 -0
- package/dist/daemon-BUgNvMUh.mjs.map +1 -0
- package/dist/daemon-DL0eglMJ.mjs +18 -0
- package/dist/daemon-mcp/index.mjs +12 -9
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{embeddings-CcscYWwk.mjs → embeddings-CEBGrzwu.mjs} +1 -1
- package/dist/{embeddings-Bx3q0QOY.mjs → embeddings-DOLZnT1X.mjs} +1 -1
- package/dist/{embeddings-Bx3q0QOY.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
- package/dist/{env-JNEIrQWg.mjs → env-DiolswKQ.mjs} +1 -1
- package/dist/{env-JNEIrQWg.mjs.map → env-DiolswKQ.mjs.map} +1 -1
- package/dist/factory-DrBg24UT.mjs +8 -0
- package/dist/factory-Ypf8r0fK.mjs +2892 -0
- package/dist/factory-Ypf8r0fK.mjs.map +1 -0
- package/dist/{fallback-CIiq2Raw.mjs → fallback-D51R4shY.mjs} +23 -11
- package/dist/fallback-D51R4shY.mjs.map +1 -0
- package/dist/{chunker-BH4i-F2b.mjs → helpers-CZsi_49C.mjs} +221 -2
- package/dist/helpers-CZsi_49C.mjs.map +1 -0
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/index.mjs +6 -7
- package/dist/{ipc-client-D16Xw6Uo.mjs → ipc-client-BLYX51nG.mjs} +2 -2
- package/dist/{ipc-client-D16Xw6Uo.mjs.map → ipc-client-BLYX51nG.mjs.map} +1 -1
- package/dist/main-resolver-BYgP86C1.mjs +7 -0
- package/dist/{main-resolver-DPPwHNtn.mjs → main-resolver-kVkMJZUD.mjs} +6 -6
- package/dist/{main-resolver-DPPwHNtn.mjs.map → main-resolver-kVkMJZUD.mjs.map} +1 -1
- package/dist/{migrate-BD7D8EEh.mjs → migrate-Bq0esMOa.mjs} +2 -2
- package/dist/{migrate-BD7D8EEh.mjs.map → migrate-Bq0esMOa.mjs.map} +1 -1
- package/dist/{pai-marker-D1MMswkz.mjs → pai-marker-DXVpFsYz.mjs} +1 -1
- package/dist/{pai-marker-D1MMswkz.mjs.map → pai-marker-DXVpFsYz.mjs.map} +1 -1
- package/dist/{planner-Dm5Acyo1.mjs → planner-D5yt0EfA.mjs} +10 -7
- package/dist/{planner-Dm5Acyo1.mjs.map → planner-D5yt0EfA.mjs.map} +1 -1
- package/dist/postgres-BmLr0MUm.mjs +5 -0
- package/dist/{postgres-Ceqsa64C.mjs → postgres-D3xc2RB4.mjs} +364 -9
- package/dist/postgres-D3xc2RB4.mjs.map +1 -0
- package/dist/{program-JJ0TwSHI.mjs → program-DIXRtAFy.mjs} +77 -70
- package/dist/program-DIXRtAFy.mjs.map +1 -0
- package/dist/{query-feedback-BIaZTTFO.mjs → query-feedback-B7FYE4JR.mjs} +1 -1
- package/dist/{query-feedback-BIaZTTFO.mjs.map → query-feedback-B7FYE4JR.mjs.map} +1 -1
- package/dist/{reranker-DKv80KO5.mjs → reranker-3lnggwgq.mjs} +1 -1
- package/dist/{reranker-DKv80KO5.mjs.map → reranker-3lnggwgq.mjs.map} +1 -1
- package/dist/{reranker-CFiEzHQu.mjs → reranker-CZ2mP4cf.mjs} +1 -1
- package/dist/router-BK-hFeQ6.mjs +3 -0
- package/dist/{router-a5G7q8_3.mjs → router-BaTbc9VX.mjs} +2 -2
- package/dist/{router-a5G7q8_3.mjs.map → router-BaTbc9VX.mjs.map} +1 -1
- package/dist/{run-BCpbxqA5.mjs → run-CLYTOm0x.mjs} +599 -22
- package/dist/run-CLYTOm0x.mjs.map +1 -0
- package/dist/{run-env-DRcp7A8K.mjs → run-env-Bnmf6bRI.mjs} +2 -2
- package/dist/{run-env-DRcp7A8K.mjs.map → run-env-Bnmf6bRI.mjs.map} +1 -1
- package/dist/{runtime-paths-CHTg3ywb.mjs → runtime-paths-D3FKAm69.mjs} +1 -1
- package/dist/{runtime-paths-CHTg3ywb.mjs.map → runtime-paths-D3FKAm69.mjs.map} +1 -1
- package/dist/{search-BOSphCJ1.mjs → search-CNAGTiJP.mjs} +2 -2
- package/dist/{search-BOSphCJ1.mjs.map → search-CNAGTiJP.mjs.map} +1 -1
- package/dist/{sources-CijVso3n.mjs → sources-CM2g-CLT.mjs} +2 -2
- package/dist/{sources-CijVso3n.mjs.map → sources-CM2g-CLT.mjs.map} +1 -1
- package/dist/{stop-words-BdQuaE9K.mjs → stop-words-DtxaTWU_.mjs} +1 -1
- package/dist/{stop-words-BdQuaE9K.mjs.map → stop-words-DtxaTWU_.mjs.map} +1 -1
- package/dist/{utils-C9HsYDpO.mjs → utils-DddRwMsG.mjs} +2 -2
- package/dist/{utils-C9HsYDpO.mjs.map → utils-DddRwMsG.mjs.map} +1 -1
- package/dist/{zettelkasten-CtGHQWPU.mjs → zettelkasten-BhZMvmoK.mjs} +149 -7
- package/dist/zettelkasten-BhZMvmoK.mjs.map +1 -0
- package/dist/zettelkasten-BwV1pluL.mjs +5 -0
- package/docs/commands/worker.md +2 -0
- package/package.json +1 -1
- package/dist/async-C2Bm_Lal.mjs +0 -300
- package/dist/async-C2Bm_Lal.mjs.map +0 -1
- package/dist/auto-route-o0BOsXn7.mjs.map +0 -1
- package/dist/chunker-BH4i-F2b.mjs.map +0 -1
- package/dist/clusters-BCtD3fbe.mjs +0 -169
- package/dist/clusters-BCtD3fbe.mjs.map +0 -1
- package/dist/context-handover-cache-pkHzmL3c.mjs.map +0 -1
- package/dist/daemon-DXlaVCkw.mjs +0 -1741
- package/dist/daemon-DXlaVCkw.mjs.map +0 -1
- package/dist/daemon-DyORmxZu.mjs +0 -19
- package/dist/detector-BEPFsINR.mjs +0 -3
- package/dist/detector-BRTtAWrM.mjs +0 -65
- package/dist/detector-BRTtAWrM.mjs.map +0 -1
- package/dist/factory-CNpPQQ-5.mjs +0 -249
- package/dist/factory-CNpPQQ-5.mjs.map +0 -1
- package/dist/factory-DY7x73mE.mjs +0 -3
- package/dist/fallback-CIiq2Raw.mjs.map +0 -1
- package/dist/federation-db-BTyoufBh.mjs +0 -139
- package/dist/federation-db-BTyoufBh.mjs.map +0 -1
- package/dist/federation-db-HfIFc7FG.mjs +0 -3
- package/dist/helpers-BRCJg0G3.mjs +0 -223
- package/dist/helpers-BRCJg0G3.mjs.map +0 -1
- package/dist/indexer-backend-DFF2FrYx.mjs +0 -5
- package/dist/indexer-backend-isSLg6yE.mjs +0 -1
- package/dist/kg-entity-DCOcsVFD.mjs +0 -29
- package/dist/kg-entity-DCOcsVFD.mjs.map +0 -1
- package/dist/latent-ideas-wwAXquGe.mjs +0 -191
- package/dist/latent-ideas-wwAXquGe.mjs.map +0 -1
- package/dist/link-boost-CnI7UVMJ.mjs +0 -34
- package/dist/link-boost-CnI7UVMJ.mjs.map +0 -1
- package/dist/main-resolver-BKz_OuEh.mjs +0 -7
- package/dist/merge-2gqRPFu2.mjs +0 -3
- package/dist/merge-DgU9OgZy.mjs +0 -6
- package/dist/merge-DgU9OgZy.mjs.map +0 -1
- package/dist/module-paths-DdRzbkUI.mjs +0 -44
- package/dist/module-paths-DdRzbkUI.mjs.map +0 -1
- package/dist/neighborhood-2FsmzoxG.mjs +0 -114
- package/dist/neighborhood-2FsmzoxG.mjs.map +0 -1
- package/dist/note-context-De63k8na.mjs +0 -106
- package/dist/note-context-De63k8na.mjs.map +0 -1
- package/dist/postgres-Ceqsa64C.mjs.map +0 -1
- package/dist/program-JJ0TwSHI.mjs.map +0 -1
- package/dist/query-feedback-B_iigYj-.mjs +0 -3
- package/dist/registry-db-C7voqML9.mjs +0 -213
- package/dist/registry-db-C7voqML9.mjs.map +0 -1
- package/dist/registry-db-JHPhA8vF.mjs +0 -3
- package/dist/registry-postgres-nOfyBSIW.mjs +0 -795
- package/dist/registry-postgres-nOfyBSIW.mjs.map +0 -1
- package/dist/registry-sqlite-DrCW1aRK.mjs +0 -593
- package/dist/registry-sqlite-DrCW1aRK.mjs.map +0 -1
- package/dist/router-CXUGsv85.mjs +0 -3
- package/dist/run-BCpbxqA5.mjs.map +0 -1
- package/dist/search-Bf3Kub3F.mjs +0 -3
- package/dist/server-DgmAHyFK.mjs +0 -403
- package/dist/server-DgmAHyFK.mjs.map +0 -1
- package/dist/session-keepalive-DxjTHGcK.mjs +0 -582
- package/dist/session-keepalive-DxjTHGcK.mjs.map +0 -1
- package/dist/sqlite-CqsTy6xo.mjs +0 -936
- package/dist/sqlite-CqsTy6xo.mjs.map +0 -1
- package/dist/state-DW8zdweW.mjs +0 -3
- package/dist/state-HyjqTihC.mjs +0 -76
- package/dist/state-HyjqTihC.mjs.map +0 -1
- package/dist/themes-CTaOj3e1.mjs +0 -148
- package/dist/themes-CTaOj3e1.mjs.map +0 -1
- package/dist/tools-BcOKPBtg.mjs +0 -1489
- package/dist/tools-BcOKPBtg.mjs.map +0 -1
- package/dist/tools-C-_l0o8Z.mjs +0 -6
- package/dist/trace-CtV0RO6n.mjs +0 -137
- package/dist/trace-CtV0RO6n.mjs.map +0 -1
- package/dist/utils-BrX9FLeK.mjs +0 -3
- package/dist/vault-indexer-BG1sEVyM.mjs +0 -537
- package/dist/vault-indexer-BG1sEVyM.mjs.map +0 -1
- package/dist/wakeup-D8n33hYV.mjs +0 -335
- package/dist/wakeup-D8n33hYV.mjs.map +0 -1
- package/dist/work-queue-worker-4tLa5gpQ.mjs +0 -567
- package/dist/work-queue-worker-4tLa5gpQ.mjs.map +0 -1
- package/dist/work-queue-worker-rFZvTO3p.mjs +0 -11
- package/dist/zettelkasten-CtGHQWPU.mjs.map +0 -1
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
import { A as archiveSessionFilesToSessionsDir, D as appendCheckpoint, E as addWorkToSessionNote, F as performScan, O as finalizeSessionNote, _ as spawnSummarizer, a as enqueue, b as updateTodoContinue, f as contentToText$2, h as handleSessionSummary, i as dequeue, j as findNotesDir, k as getCurrentNotePath, l as markCompleted, m as getGitContext, n as writeContextHandoverCache, p as findLatestJsonl, r as cleanup, s as hasPendingOrProcessingOfType, t as readContextHandoverCache, u as markFailed, y as sessionIdFromTranscript } from "./context-handover-cache-pkHzmL3c.mjs";
|
|
2
|
-
import { d as registryBackend, k as storageBackend } from "./state-HyjqTihC.mjs";
|
|
3
|
-
import { t as detectTopicShift } from "./detector-BRTtAWrM.mjs";
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { basename, dirname, join } from "node:path";
|
|
6
|
-
|
|
7
|
-
//#region src/daemon/templates/context-handover-prompt.ts
|
|
8
|
-
function buildContextHandoverPrompt(params) {
|
|
9
|
-
const { turns, gitLog, cwd, previousHandover } = params;
|
|
10
|
-
const turnsSection = turns.length > 0 ? turns.join("\n\n") : "(No turns extracted)";
|
|
11
|
-
const gitSection = gitLog.trim() || "(No git commits during this session)";
|
|
12
|
-
return `This session is approaching a context-window compaction. Write the \
|
|
13
|
-
handover that would let a successor continue WITHOUT re-reading this \
|
|
14
|
-
conversation and WITHOUT asking the user to repeat themselves.
|
|
15
|
-
|
|
16
|
-
Project directory: ${cwd}
|
|
17
|
-
${previousHandover ? `\nA PREVIOUS HANDOVER already exists for this session (from an earlier, \
|
|
18
|
-
lower threshold). Carry it forward — start your response with a one-line \
|
|
19
|
-
pointer to it ("Carries forward from the previous handover, which stays for \
|
|
20
|
-
its detail.") and then write only what has happened SINCE it that a compact \
|
|
21
|
-
would also lose. Do not re-derive or repeat what it already covered.\n\n\
|
|
22
|
-
PREVIOUS HANDOVER:\n${previousHandover}\n` : ""}
|
|
23
|
-
SCOPING RULE — the single most important instruction here: this is a DIFF \
|
|
24
|
-
against everything already durable, not a summary of the session. Assume \
|
|
25
|
-
the successor can and will read git history, the issue tracker, and any \
|
|
26
|
-
notes files themselves. Do NOT restate anything recoverable there — no file \
|
|
27
|
-
inventories, no "implemented X" restatements, no chronology of commands run. \
|
|
28
|
-
Write down ONLY what existed exclusively in this conversation and would \
|
|
29
|
-
otherwise be gone: the reasoning, not the result.
|
|
30
|
-
|
|
31
|
-
Write ONLY the sections below that this session actually has content for — \
|
|
32
|
-
omit a section entirely rather than writing "none" or padding it:
|
|
33
|
-
|
|
34
|
-
WHERE THIS SESSION ENDED — the concrete state right now: what is verified \
|
|
35
|
-
working (tests green, tree clean, etc.) versus merely attempted, any branch \
|
|
36
|
-
or working-tree state that isn't obvious from git status alone (e.g. a \
|
|
37
|
-
detached HEAD, an intentionally uncommitted change, a stash).
|
|
38
|
-
|
|
39
|
-
WHAT CLOSED — each item paired with the evidence that closed it, not just \
|
|
40
|
-
the claim that it did.
|
|
41
|
-
|
|
42
|
-
WHAT IS FILED AND STILL OPEN — each with its next concrete step or who/what \
|
|
43
|
-
it is waiting on.
|
|
44
|
-
|
|
45
|
-
DECISIONS & REASONING — choices actually made, each with why this way and \
|
|
46
|
-
not another way.
|
|
47
|
-
|
|
48
|
-
REJECTED ALTERNATIVES — approaches considered and set aside, and why. An \
|
|
49
|
-
alternative without a reason is not worth recording.
|
|
50
|
-
|
|
51
|
-
NEGATIVE RESULTS — something investigated that turned out NOT to be the \
|
|
52
|
-
answer, kept WITH the reasoning that ruled it out (e.g. "X looked like the \
|
|
53
|
-
cause — it wasn't; the actual mechanism was Y"). Without the reasoning \
|
|
54
|
-
attached, a successor re-investigates the same dead end.
|
|
55
|
-
|
|
56
|
-
TRAPS — anything discovered that is invisible from the repo alone and would \
|
|
57
|
-
otherwise bite a successor immediately (a required manual step, an ordering \
|
|
58
|
-
constraint, a state that looks fine but isn't).
|
|
59
|
-
|
|
60
|
-
CRITICAL VALUES — any specific id, path, URL, credential name, version \
|
|
61
|
-
number, or configuration value that appeared in conversation and whose loss \
|
|
62
|
-
would force someone to go find it again.
|
|
63
|
-
|
|
64
|
-
THE FEW THINGS THAT MATTER MOST, IF NOTHING ELSE IS READ — at most 3 bullets. \
|
|
65
|
-
Forces a priority judgement; do not pad this to fill space.
|
|
66
|
-
|
|
67
|
-
Format your response as markdown with those section headers (only the ones \
|
|
68
|
-
you have content for), no other structure.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
CONVERSATION:
|
|
73
|
-
${turnsSection}
|
|
74
|
-
|
|
75
|
-
GIT COMMITS:
|
|
76
|
-
${gitSection}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
//#endregion
|
|
80
|
-
//#region src/daemon/context-handover-worker.ts
|
|
81
|
-
/**
|
|
82
|
-
* context-handover-worker.ts — the threshold-triggered pre-compaction
|
|
83
|
-
* handover.
|
|
84
|
-
*
|
|
85
|
-
* `session-summary-worker.ts` already spawns a model to write session notes,
|
|
86
|
-
* but only from the PreCompact hook itself — which fires at the compaction
|
|
87
|
-
* boundary, far too late to be injected INTO that same compaction's
|
|
88
|
-
* post-compact context. This worker runs earlier, off a token-count
|
|
89
|
-
* threshold (see ../hooks/ts/lib/context-fill.ts), so a model-written
|
|
90
|
-
* handover already exists in a cache file by the time compaction happens.
|
|
91
|
-
*
|
|
92
|
-
* Deliberately narrow: this does not write a session note, does not touch
|
|
93
|
-
* the session-summary cooldown file, and does not run KG extraction. It
|
|
94
|
-
* writes one thing — a cache file the PreCompact hook can read — because its
|
|
95
|
-
* only job is to exist before compaction does, not to duplicate what
|
|
96
|
-
* session-summary-worker already does well.
|
|
97
|
-
*
|
|
98
|
-
* The prompt itself (see templates/context-handover-prompt.ts) treats the
|
|
99
|
-
* handover as a DIFF against everything already durable — git, the tracker,
|
|
100
|
-
* the notes — not a session summary, and a refresh run carries the previous
|
|
101
|
-
* cached handover forward rather than overwriting it.
|
|
102
|
-
*/
|
|
103
|
-
/** How much of the tail of the transcript to feed the summarizer. Generous —
|
|
104
|
-
* this runs at most twice per session, not on every tool call. */
|
|
105
|
-
const MAX_TURN_CHARS = 15e4;
|
|
106
|
-
/** Max turns to include (a hard floor even if MAX_TURN_CHARS is not hit). */
|
|
107
|
-
const MAX_TURNS = 120;
|
|
108
|
-
/**
|
|
109
|
-
* Interleaved "User:"/"Assistant:" turns, oldest-kept-from-the-end so a long
|
|
110
|
-
* transcript is truncated to its most recent content rather than its
|
|
111
|
-
* earliest. Unlike session-summary-worker's extractFromJsonl (which keeps
|
|
112
|
-
* only user messages, because a session note doesn't need the assistant's
|
|
113
|
-
* own reasoning) this keeps BOTH sides — the whole point of this prompt is
|
|
114
|
-
* the assistant's reasoning, which lives in assistant turns.
|
|
115
|
-
*/
|
|
116
|
-
function extractTurns(jsonlPath) {
|
|
117
|
-
let raw;
|
|
118
|
-
try {
|
|
119
|
-
raw = readFileSync(jsonlPath, "utf-8");
|
|
120
|
-
} catch {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
if (raw.length > MAX_TURN_CHARS) {
|
|
124
|
-
const truncPoint = raw.indexOf("\n", raw.length - MAX_TURN_CHARS);
|
|
125
|
-
raw = truncPoint >= 0 ? raw.slice(truncPoint + 1) : raw.slice(-MAX_TURN_CHARS);
|
|
126
|
-
}
|
|
127
|
-
const turns = [];
|
|
128
|
-
for (const line of raw.trim().split("\n")) {
|
|
129
|
-
if (!line.trim()) continue;
|
|
130
|
-
let entry;
|
|
131
|
-
try {
|
|
132
|
-
entry = JSON.parse(line);
|
|
133
|
-
} catch {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (entry.type === "user") {
|
|
137
|
-
const msg = entry.message;
|
|
138
|
-
const text = contentToText$2(msg?.content);
|
|
139
|
-
if (text && text.length >= 3 && !text.startsWith("<system-reminder>")) turns.push(`User: ${text.slice(0, 2e3)}`);
|
|
140
|
-
} else if (entry.type === "assistant") {
|
|
141
|
-
const msg = entry.message;
|
|
142
|
-
const text = contentToText$2(msg?.content);
|
|
143
|
-
if (text && text.length >= 3) turns.push(`Assistant: ${text.slice(0, 4e3)}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return turns.slice(-MAX_TURNS);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Process a `context-handover` work item.
|
|
150
|
-
*
|
|
151
|
-
* THROWS on every failure path (missing input, no transcript, no turns, an
|
|
152
|
-
* empty/failed summarizer run) rather than logging and returning silently.
|
|
153
|
-
* That used to be a deliberate choice — avoid the work queue's own retry
|
|
154
|
-
* piling up redundant LLM calls — but it made a real failure invisible: a
|
|
155
|
-
* daemon restart mid-spawn (session 77084e72-...) left no cache, no error
|
|
156
|
-
* anywhere the work queue's own stats could show, and a trigger-side marker
|
|
157
|
-
* that (at the time) had already been set to "done". Throwing here makes
|
|
158
|
-
* work-queue-worker.ts call markFailed(), which logs prominently AND is
|
|
159
|
-
* visible via `work_queue_stats` / `pai daemon status` — not just a stderr
|
|
160
|
-
* line that can scroll away. It is a second, faster (seconds-to-minutes
|
|
161
|
-
* backoff) retry path alongside the primary one in context-handover-
|
|
162
|
-
* trigger.ts (which retries on its own ~5-minute cadence based on whether a
|
|
163
|
-
* cache actually appeared); the two are complementary, not redundant — a
|
|
164
|
-
* failed cache write costs nothing to retry twice.
|
|
165
|
-
*/
|
|
166
|
-
async function handleContextHandover(payload) {
|
|
167
|
-
const { cwd, sessionId, transcriptPath, threshold, urgent } = payload;
|
|
168
|
-
if (!cwd || !sessionId) throw new Error("[context-handover] payload missing cwd or sessionId");
|
|
169
|
-
process.stderr.write(`[context-handover] Starting for ${cwd} (session=${sessionId}, threshold=${threshold}${urgent ? ", urgent" : ""}).\n`);
|
|
170
|
-
let jsonlPath = transcriptPath && existsSync(transcriptPath) ? transcriptPath : null;
|
|
171
|
-
if (!jsonlPath) jsonlPath = findLatestJsonl(cwd);
|
|
172
|
-
if (!jsonlPath) throw new Error(`[context-handover] No transcript found for ${cwd} (session=${sessionId})`);
|
|
173
|
-
const turns = extractTurns(jsonlPath);
|
|
174
|
-
if (turns.length === 0) throw new Error(`[context-handover] No turns extracted from ${jsonlPath} (session=${sessionId})`);
|
|
175
|
-
const prompt = buildContextHandoverPrompt({
|
|
176
|
-
turns,
|
|
177
|
-
gitLog: await getGitContext(cwd),
|
|
178
|
-
cwd,
|
|
179
|
-
previousHandover: readContextHandoverCache(sessionId)?.summary
|
|
180
|
-
});
|
|
181
|
-
process.stderr.write(`[context-handover] Sending ${prompt.length} char prompt to sonnet...\n`);
|
|
182
|
-
const summary = await spawnSummarizer(prompt, "sonnet");
|
|
183
|
-
if (!summary || !summary.trim()) throw new Error(`[context-handover] sonnet produced no output (session=${sessionId})`);
|
|
184
|
-
writeContextHandoverCache({
|
|
185
|
-
sessionId,
|
|
186
|
-
cwd,
|
|
187
|
-
threshold,
|
|
188
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
|
-
model: "sonnet",
|
|
190
|
-
summary: summary.trim()
|
|
191
|
-
});
|
|
192
|
-
process.stderr.write(`[context-handover] Wrote cache for session ${sessionId} (${summary.length} chars).\n`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
//#endregion
|
|
196
|
-
//#region src/daemon/topic-detect-worker.ts
|
|
197
|
-
/**
|
|
198
|
-
* topic-detect-worker.ts — Topic shift detection for session note splitting
|
|
199
|
-
*
|
|
200
|
-
* Processes `topic-detect` work items by:
|
|
201
|
-
* 1. Extracting recent user messages from the JSONL transcript
|
|
202
|
-
* 2. Running the BM25-based topic shift detector against the PAI memory DB
|
|
203
|
-
* 3. If a shift is detected, recording a topic boundary marker
|
|
204
|
-
*
|
|
205
|
-
* The actual note splitting is handled by session-summary-worker.ts when it
|
|
206
|
-
* processes the next `session-summary` work item — it uses the TOPIC: line
|
|
207
|
-
* from the summarizer to decide whether to create a new note.
|
|
208
|
-
*
|
|
209
|
-
* This worker provides an additional signal: project-level topic shift
|
|
210
|
-
* (e.g., conversation moved from project A to project B). The session
|
|
211
|
-
* summary worker handles intra-project topic shifts (e.g., from "dark mode"
|
|
212
|
-
* to "keyboard IPC" within the same project).
|
|
213
|
-
*/
|
|
214
|
-
const MAX_CONTEXT_MESSAGES = 5;
|
|
215
|
-
const MAX_CONTEXT_CHARS = 2e3;
|
|
216
|
-
/**
|
|
217
|
-
* Extract recent user messages from a JSONL transcript for topic detection.
|
|
218
|
-
* Takes only the last few messages to represent the current topic.
|
|
219
|
-
*/
|
|
220
|
-
function extractRecentContext(jsonlPath) {
|
|
221
|
-
try {
|
|
222
|
-
const raw = readFileSync(jsonlPath, "utf-8");
|
|
223
|
-
const lines = (raw.length > 5e4 ? raw.slice(-5e4) : raw).trim().split("\n");
|
|
224
|
-
const messages = [];
|
|
225
|
-
for (let i = lines.length - 1; i >= 0 && messages.length < MAX_CONTEXT_MESSAGES; i--) {
|
|
226
|
-
const line = lines[i].trim();
|
|
227
|
-
if (!line) continue;
|
|
228
|
-
try {
|
|
229
|
-
const entry = JSON.parse(line);
|
|
230
|
-
if (entry.type === "user") {
|
|
231
|
-
const msg = entry.message;
|
|
232
|
-
if (msg?.content) {
|
|
233
|
-
const text = contentToText$1(msg.content);
|
|
234
|
-
if (text && text.length > 3) messages.unshift(text.slice(0, 500));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
} catch {}
|
|
238
|
-
}
|
|
239
|
-
return messages.join("\n\n").slice(0, MAX_CONTEXT_CHARS);
|
|
240
|
-
} catch {
|
|
241
|
-
return "";
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/** Convert Claude content (string or content block array) to plain text. */
|
|
245
|
-
function contentToText$1(content) {
|
|
246
|
-
if (typeof content === "string") return content;
|
|
247
|
-
if (Array.isArray(content)) return content.map((c) => {
|
|
248
|
-
if (typeof c === "string") return c;
|
|
249
|
-
const block = c;
|
|
250
|
-
if (block?.text) return String(block.text);
|
|
251
|
-
if (block?.content) return String(block.content);
|
|
252
|
-
return "";
|
|
253
|
-
}).join(" ").trim();
|
|
254
|
-
return "";
|
|
255
|
-
}
|
|
256
|
-
const TOPIC_BOUNDARY_FILE = "topic-boundary.json";
|
|
257
|
-
/**
|
|
258
|
-
* Write a topic boundary marker into the Notes directory.
|
|
259
|
-
* The session-summary-worker checks for this file and uses it as an
|
|
260
|
-
* additional signal that a new note should be created.
|
|
261
|
-
*/
|
|
262
|
-
function writeTopicBoundary(cwd, boundary) {
|
|
263
|
-
try {
|
|
264
|
-
const boundaryPath = join(findNotesDir(cwd).path, TOPIC_BOUNDARY_FILE);
|
|
265
|
-
writeFileSync(boundaryPath, JSON.stringify(boundary, null, 2), "utf-8");
|
|
266
|
-
process.stderr.write(`[topic-detect] Wrote topic boundary marker: ${boundaryPath}\n`);
|
|
267
|
-
} catch (e) {
|
|
268
|
-
process.stderr.write(`[topic-detect] Could not write boundary marker: ${e}\n`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Process a `topic-detect` work item.
|
|
273
|
-
*
|
|
274
|
-
* Called by work-queue-worker.ts. Throws on fatal errors so the work queue
|
|
275
|
-
* retry logic handles them.
|
|
276
|
-
*/
|
|
277
|
-
async function handleTopicDetect(payload) {
|
|
278
|
-
const { cwd, currentProject, transcriptPath, sessionId } = payload;
|
|
279
|
-
if (!cwd) throw new Error("topic-detect payload missing cwd");
|
|
280
|
-
process.stderr.write(`[topic-detect] Starting for ${cwd}${currentProject ? ` (project=${currentProject})` : ""}${sessionId ? ` (session=${sessionId})` : ""}\n`);
|
|
281
|
-
if (!registryBackend || !storageBackend) {
|
|
282
|
-
process.stderr.write("[topic-detect] Registry backend or storage backend not available — skipping.\n");
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
let context = payload.context || "";
|
|
286
|
-
if (!context && transcriptPath && existsSync(transcriptPath)) context = extractRecentContext(transcriptPath);
|
|
287
|
-
if (!context || context.trim().length < 10) {
|
|
288
|
-
process.stderr.write("[topic-detect] Insufficient context for topic detection — skipping.\n");
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
process.stderr.write(`[topic-detect] Context: ${context.length} chars, checking against memory...\n`);
|
|
292
|
-
const result = await detectTopicShift(registryBackend, storageBackend, {
|
|
293
|
-
context,
|
|
294
|
-
currentProject,
|
|
295
|
-
threshold: .6,
|
|
296
|
-
candidates: 20
|
|
297
|
-
});
|
|
298
|
-
process.stderr.write(`[topic-detect] Result: shifted=${result.shifted}, suggested=${result.suggestedProject}, confidence=${result.confidence.toFixed(2)}, chunks=${result.chunkCount}\n`);
|
|
299
|
-
if (result.topProjects.length > 0) process.stderr.write(`[topic-detect] Top projects: ${result.topProjects.map((p) => `${p.slug}(${(p.score * 100).toFixed(0)}%)`).join(", ")}\n`);
|
|
300
|
-
if (result.shifted) {
|
|
301
|
-
writeTopicBoundary(cwd, {
|
|
302
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
303
|
-
previousProject: result.currentProject,
|
|
304
|
-
suggestedProject: result.suggestedProject,
|
|
305
|
-
confidence: result.confidence,
|
|
306
|
-
context: context.slice(0, 200)
|
|
307
|
-
});
|
|
308
|
-
try {
|
|
309
|
-
const notePath = getCurrentNotePath(findNotesDir(cwd).path);
|
|
310
|
-
if (notePath) appendCheckpoint(notePath, `Topic shift detected: conversation moved from **${result.currentProject}** to **${result.suggestedProject}** (confidence: ${(result.confidence * 100).toFixed(0)}%). A new session note will be created for the new topic.`);
|
|
311
|
-
} catch (e) {
|
|
312
|
-
process.stderr.write(`[topic-detect] Could not append checkpoint: ${e}\n`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
process.stderr.write("[topic-detect] Done.\n");
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
//#endregion
|
|
319
|
-
//#region src/daemon/work-queue-worker.ts
|
|
320
|
-
/**
|
|
321
|
-
* work-queue-worker.ts — Daemon worker loop for the persistent work queue
|
|
322
|
-
*
|
|
323
|
-
* Runs every 5 seconds to drain the queue.
|
|
324
|
-
* Handles 'session-end' work items by reading the transcript, extracting
|
|
325
|
-
* work summaries, updating the session note, and updating TODO.md.
|
|
326
|
-
* Handles 'session-summary' items by spawning Haiku for AI-powered note generation.
|
|
327
|
-
* Handles 'context-handover' items by spawning Sonnet to write the
|
|
328
|
-
* threshold-triggered pre-compaction reasoning handover (see
|
|
329
|
-
* context-handover-worker.ts and hooks/ts/lib/context-fill.ts).
|
|
330
|
-
* Handles 'registry-scan' items by running performScan() against the registry DB.
|
|
331
|
-
*
|
|
332
|
-
* Handles 'topic-detect' items by running BM25-based topic shift detection.
|
|
333
|
-
* Other item types (note-update, todo-update) are stubs — they log and
|
|
334
|
-
* complete immediately, ready for future expansion.
|
|
335
|
-
*/
|
|
336
|
-
const WORKER_INTERVAL_MS = 5e3;
|
|
337
|
-
const HOUSEKEEPING_INTERVAL_MS = 600 * 1e3;
|
|
338
|
-
let workerTimer = null;
|
|
339
|
-
let housekeepingTimer = null;
|
|
340
|
-
/** Start the background worker and housekeeping timers. */
|
|
341
|
-
function startWorker() {
|
|
342
|
-
process.stderr.write("[work-queue-worker] Starting worker loop.\n");
|
|
343
|
-
workerTimer = setInterval(async () => {
|
|
344
|
-
try {
|
|
345
|
-
await processNextItem();
|
|
346
|
-
} catch (e) {
|
|
347
|
-
process.stderr.write(`[work-queue-worker] Uncaught error in worker loop: ${e}\n`);
|
|
348
|
-
}
|
|
349
|
-
}, WORKER_INTERVAL_MS);
|
|
350
|
-
housekeepingTimer = setInterval(() => {
|
|
351
|
-
try {
|
|
352
|
-
cleanup();
|
|
353
|
-
} catch (e) {
|
|
354
|
-
process.stderr.write(`[work-queue-worker] Housekeeping error: ${e}\n`);
|
|
355
|
-
}
|
|
356
|
-
}, HOUSEKEEPING_INTERVAL_MS);
|
|
357
|
-
process.stderr.write("[work-queue-worker] Worker started (interval=5s, housekeeping=10min).\n");
|
|
358
|
-
}
|
|
359
|
-
/** Stop the worker timers gracefully. */
|
|
360
|
-
function stopWorker() {
|
|
361
|
-
if (workerTimer !== null) {
|
|
362
|
-
clearInterval(workerTimer);
|
|
363
|
-
workerTimer = null;
|
|
364
|
-
}
|
|
365
|
-
if (housekeepingTimer !== null) {
|
|
366
|
-
clearInterval(housekeepingTimer);
|
|
367
|
-
housekeepingTimer = null;
|
|
368
|
-
}
|
|
369
|
-
process.stderr.write("[work-queue-worker] Worker stopped.\n");
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Signal that new work has been enqueued.
|
|
373
|
-
* The worker will run on its next tick — we don't need to reset the timer
|
|
374
|
-
* since 5 s is fast enough. The flag allows future optimisations.
|
|
375
|
-
*/
|
|
376
|
-
function notifyNewWork() {}
|
|
377
|
-
async function processNextItem() {
|
|
378
|
-
const item = dequeue();
|
|
379
|
-
if (!item) return;
|
|
380
|
-
process.stderr.write(`[work-queue-worker] Processing ${item.type} (id=${item.id}, attempt=${item.attempts}).\n`);
|
|
381
|
-
try {
|
|
382
|
-
switch (item.type) {
|
|
383
|
-
case "session-end":
|
|
384
|
-
await handleSessionEnd(item);
|
|
385
|
-
break;
|
|
386
|
-
case "session-summary":
|
|
387
|
-
await handleSessionSummary(item.payload);
|
|
388
|
-
break;
|
|
389
|
-
case "context-handover":
|
|
390
|
-
await handleContextHandover(item.payload);
|
|
391
|
-
break;
|
|
392
|
-
case "topic-detect":
|
|
393
|
-
await handleTopicDetect(item.payload);
|
|
394
|
-
break;
|
|
395
|
-
case "registry-scan":
|
|
396
|
-
await handleRegistryScan();
|
|
397
|
-
break;
|
|
398
|
-
case "note-update":
|
|
399
|
-
case "todo-update":
|
|
400
|
-
process.stderr.write(`[work-queue-worker] Item type '${item.type}' is not yet implemented — completing as no-op.\n`);
|
|
401
|
-
break;
|
|
402
|
-
default: throw new Error(`Unknown work item type: ${item.type}`);
|
|
403
|
-
}
|
|
404
|
-
markCompleted(item.id);
|
|
405
|
-
process.stderr.write(`[work-queue-worker] Completed ${item.type} (id=${item.id}).\n`);
|
|
406
|
-
} catch (e) {
|
|
407
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
408
|
-
markFailed(item.id, msg);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Process a 'session-end' work item.
|
|
413
|
-
*
|
|
414
|
-
* Expected payload:
|
|
415
|
-
* transcriptPath: string — absolute path to the .jsonl transcript
|
|
416
|
-
* cwd: string — working directory of the session
|
|
417
|
-
* message?: string — COMPLETED: line extracted by the hook (optional)
|
|
418
|
-
*/
|
|
419
|
-
async function handleSessionEnd(item) {
|
|
420
|
-
const { transcriptPath, cwd, message: hookMessage } = item.payload;
|
|
421
|
-
if (!transcriptPath) throw new Error("session-end payload missing transcriptPath");
|
|
422
|
-
if (!cwd) throw new Error("session-end payload missing cwd");
|
|
423
|
-
let transcript;
|
|
424
|
-
try {
|
|
425
|
-
transcript = readFileSync(transcriptPath, "utf-8");
|
|
426
|
-
} catch (e) {
|
|
427
|
-
throw new Error(`Could not read transcript at ${transcriptPath}: ${e}`);
|
|
428
|
-
}
|
|
429
|
-
const lines = transcript.trim().split("\n");
|
|
430
|
-
const workItems = extractWorkFromTranscript(lines);
|
|
431
|
-
let message = hookMessage ?? "";
|
|
432
|
-
if (!message) {
|
|
433
|
-
const lastEntry = tryParseJson(lines[lines.length - 1]);
|
|
434
|
-
if (lastEntry?.type === "assistant" && lastEntry.message?.content) {
|
|
435
|
-
const m = contentToText(lastEntry.message.content).match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
436
|
-
if (m) message = m[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "").trim();
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
const currentNotePath = getCurrentNotePath(findNotesDir(cwd).path);
|
|
440
|
-
if (currentNotePath) {
|
|
441
|
-
if (workItems.length > 0) {
|
|
442
|
-
addWorkToSessionNote(currentNotePath, workItems);
|
|
443
|
-
process.stderr.write(`[work-queue-worker] Added ${workItems.length} work item(s) to note.\n`);
|
|
444
|
-
} else if (message) {
|
|
445
|
-
addWorkToSessionNote(currentNotePath, [{
|
|
446
|
-
title: message,
|
|
447
|
-
completed: true
|
|
448
|
-
}]);
|
|
449
|
-
process.stderr.write("[work-queue-worker] Added completion message to note.\n");
|
|
450
|
-
}
|
|
451
|
-
finalizeSessionNote(currentNotePath, message || "Session completed.");
|
|
452
|
-
process.stderr.write(`[work-queue-worker] Finalized session note: ${basename(currentNotePath)}.\n`);
|
|
453
|
-
if (workItems.length === 0 && !message) process.stderr.write("[work-queue-worker] Nothing to hand over — leaving TODO.md ## Continue intact.\n");
|
|
454
|
-
else try {
|
|
455
|
-
const stateLines = [];
|
|
456
|
-
stateLines.push(`Working directory: ${cwd}`);
|
|
457
|
-
if (workItems.length > 0) {
|
|
458
|
-
stateLines.push("", "Work completed:");
|
|
459
|
-
for (const wi of workItems.slice(0, 5)) stateLines.push(`- ${wi.title}`);
|
|
460
|
-
}
|
|
461
|
-
if (message) stateLines.push("", `Last completed: ${message}`);
|
|
462
|
-
updateTodoContinue(cwd, basename(currentNotePath), stateLines.join("\n"), "session-end", sessionIdFromTranscript(transcriptPath));
|
|
463
|
-
} catch (todoError) {
|
|
464
|
-
process.stderr.write(`[work-queue-worker] Could not update TODO.md: ${todoError}\n`);
|
|
465
|
-
}
|
|
466
|
-
} else process.stderr.write("[work-queue-worker] No current session note found — skipping note update.\n");
|
|
467
|
-
try {
|
|
468
|
-
const archivedCount = archiveSessionFilesToSessionsDir(dirname(transcriptPath));
|
|
469
|
-
if (archivedCount > 0) process.stderr.write(`[work-queue-worker] Archived ${archivedCount} session file(s) to sessions/.\n`);
|
|
470
|
-
} catch (moveError) {
|
|
471
|
-
process.stderr.write(`[work-queue-worker] Could not move session files: ${moveError}\n`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Run performScan() against the registry DB.
|
|
476
|
-
*
|
|
477
|
-
* Debounce: if another registry-scan job is already pending in the queue,
|
|
478
|
-
* we still run this one (dequeue already picked it), but enqueueRegistryScan()
|
|
479
|
-
* checks before enqueuing so duplicates rarely reach here.
|
|
480
|
-
*/
|
|
481
|
-
async function handleRegistryScan() {
|
|
482
|
-
if (!registryBackend) throw new Error("registry-scan: registryBackend not initialized yet");
|
|
483
|
-
const t0 = Date.now();
|
|
484
|
-
process.stderr.write("[work-queue-worker] Running registry scan...\n");
|
|
485
|
-
const result = await performScan();
|
|
486
|
-
const elapsed = Date.now() - t0;
|
|
487
|
-
process.stderr.write(`[work-queue-worker] Registry scan complete: ${result.projectsScanned} projects (${result.projectsNew} new, ${result.projectsUpdated} updated), ${result.sessionsScanned} sessions (${result.sessionsNew} new) in ${elapsed}ms.\n`);
|
|
488
|
-
if (result.skipped.length > 0) process.stderr.write(`[work-queue-worker] Registry scan: ${result.skipped.length} project(s) skipped (path not found).\n`);
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Enqueue a registry-scan work item — debounced.
|
|
492
|
-
* If a registry-scan item is already pending or processing, skip enqueue.
|
|
493
|
-
*/
|
|
494
|
-
function enqueueRegistryScan() {
|
|
495
|
-
if (hasPendingOrProcessingOfType("registry-scan")) {
|
|
496
|
-
process.stderr.write("[work-queue-worker] Registry scan already pending/processing — skipping duplicate enqueue.\n");
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
enqueue({
|
|
500
|
-
type: "registry-scan",
|
|
501
|
-
priority: 5,
|
|
502
|
-
payload: {}
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
function tryParseJson(line) {
|
|
506
|
-
try {
|
|
507
|
-
return JSON.parse(line);
|
|
508
|
-
} catch {
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
function contentToText(content) {
|
|
513
|
-
if (typeof content === "string") return content;
|
|
514
|
-
if (Array.isArray(content)) return content.map((c) => {
|
|
515
|
-
if (typeof c === "string") return c;
|
|
516
|
-
const block = c;
|
|
517
|
-
if (block?.text) return String(block.text);
|
|
518
|
-
if (block?.content) return String(block.content);
|
|
519
|
-
return "";
|
|
520
|
-
}).join(" ").trim();
|
|
521
|
-
return "";
|
|
522
|
-
}
|
|
523
|
-
function extractWorkFromTranscript(lines) {
|
|
524
|
-
const workItems = [];
|
|
525
|
-
const seenSummaries = /* @__PURE__ */ new Set();
|
|
526
|
-
for (const line of lines) {
|
|
527
|
-
const entry = tryParseJson(line);
|
|
528
|
-
if (!entry || entry.type !== "assistant") continue;
|
|
529
|
-
const msg = entry.message;
|
|
530
|
-
if (!msg?.content) continue;
|
|
531
|
-
const content = contentToText(msg.content);
|
|
532
|
-
const summaryMatch = content.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
533
|
-
if (summaryMatch) {
|
|
534
|
-
const summary = summaryMatch[1].trim();
|
|
535
|
-
if (summary && !seenSummaries.has(summary) && summary.length > 5) {
|
|
536
|
-
seenSummaries.add(summary);
|
|
537
|
-
const details = [];
|
|
538
|
-
const actionsMatch = content.match(/ACTIONS:\s*(.+?)(?=\n[A-Z]+:|$)/is);
|
|
539
|
-
if (actionsMatch) {
|
|
540
|
-
const actionLines = actionsMatch[1].split("\n").map((l) => l.replace(/^[-*•]\s*/, "").replace(/^\d+\.\s*/, "").trim()).filter((l) => l.length > 3 && l.length < 100);
|
|
541
|
-
details.push(...actionLines.slice(0, 3));
|
|
542
|
-
}
|
|
543
|
-
workItems.push({
|
|
544
|
-
title: summary,
|
|
545
|
-
details: details.length > 0 ? details : void 0,
|
|
546
|
-
completed: true
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
const completedMatch = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
551
|
-
if (completedMatch && workItems.length === 0) {
|
|
552
|
-
const completed = completedMatch[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "").trim();
|
|
553
|
-
if (completed && !seenSummaries.has(completed) && completed.length > 5) {
|
|
554
|
-
seenSummaries.add(completed);
|
|
555
|
-
workItems.push({
|
|
556
|
-
title: completed,
|
|
557
|
-
completed: true
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return workItems;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
//#endregion
|
|
566
|
-
export { stopWorker as i, notifyNewWork as n, startWorker as r, enqueueRegistryScan as t };
|
|
567
|
-
//# sourceMappingURL=work-queue-worker-4tLa5gpQ.mjs.map
|