quadwork 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/quadwork.js +82 -0
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +3 -3
- package/out/__next._full.txt +12 -12
- package/out/__next._head.txt +4 -4
- package/out/__next._index.txt +6 -6
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/{18cmux34jwe.p.js → 0-y13tz~pmpno.js} +1 -1
- package/out/_next/static/chunks/{0zqyw6q.jp~1i.js → 0.9m84as-sc_r.js} +13 -13
- package/out/_next/static/chunks/05.po0c1knrbu.css +2 -0
- package/out/_next/static/chunks/084lff9v4p_vh.js +1 -0
- package/out/_next/static/chunks/0e.ktwt1nyj...js +1 -0
- package/out/_next/static/chunks/{05ok82hwk0x-c.js → 0za4cvk8.n0-y.js} +1 -1
- package/out/_not-found/__next._full.txt +11 -11
- package/out/_not-found/__next._head.txt +4 -4
- package/out/_not-found/__next._index.txt +6 -6
- package/out/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +11 -11
- package/out/app-shell/__next._full.txt +11 -11
- package/out/app-shell/__next._head.txt +4 -4
- package/out/app-shell/__next._index.txt +6 -6
- package/out/app-shell/__next._tree.txt +2 -2
- package/out/app-shell/__next.app-shell.__PAGE__.txt +2 -2
- package/out/app-shell/__next.app-shell.txt +3 -3
- package/out/app-shell.html +1 -1
- package/out/app-shell.txt +11 -11
- package/out/index.html +1 -1
- package/out/index.txt +12 -12
- package/out/project/_/__next._full.txt +12 -12
- package/out/project/_/__next._head.txt +4 -4
- package/out/project/_/__next._index.txt +6 -6
- package/out/project/_/__next._tree.txt +2 -2
- package/out/project/_/__next.project.$d$id.__PAGE__.txt +3 -3
- package/out/project/_/__next.project.$d$id.txt +3 -3
- package/out/project/_/__next.project.txt +3 -3
- package/out/project/_/memory/__next._full.txt +12 -12
- package/out/project/_/memory/__next._head.txt +4 -4
- package/out/project/_/memory/__next._index.txt +6 -6
- package/out/project/_/memory/__next._tree.txt +2 -2
- package/out/project/_/memory/__next.project.$d$id.memory.__PAGE__.txt +3 -3
- package/out/project/_/memory/__next.project.$d$id.memory.txt +3 -3
- package/out/project/_/memory/__next.project.$d$id.txt +3 -3
- package/out/project/_/memory/__next.project.txt +3 -3
- package/out/project/_/memory.html +1 -1
- package/out/project/_/memory.txt +12 -12
- package/out/project/_/queue/__next._full.txt +12 -12
- package/out/project/_/queue/__next._head.txt +4 -4
- package/out/project/_/queue/__next._index.txt +6 -6
- package/out/project/_/queue/__next._tree.txt +2 -2
- package/out/project/_/queue/__next.project.$d$id.queue.__PAGE__.txt +3 -3
- package/out/project/_/queue/__next.project.$d$id.queue.txt +3 -3
- package/out/project/_/queue/__next.project.$d$id.txt +3 -3
- package/out/project/_/queue/__next.project.txt +3 -3
- package/out/project/_/queue.html +1 -1
- package/out/project/_/queue.txt +12 -12
- package/out/project/_.html +1 -1
- package/out/project/_.txt +12 -12
- package/out/settings/__next._full.txt +12 -12
- package/out/settings/__next._head.txt +4 -4
- package/out/settings/__next._index.txt +6 -6
- package/out/settings/__next._tree.txt +2 -2
- package/out/settings/__next.settings.__PAGE__.txt +3 -3
- package/out/settings/__next.settings.txt +3 -3
- package/out/settings.html +1 -1
- package/out/settings.txt +12 -12
- package/out/setup/__next._full.txt +12 -12
- package/out/setup/__next._head.txt +4 -4
- package/out/setup/__next._index.txt +6 -6
- package/out/setup/__next._tree.txt +2 -2
- package/out/setup/__next.setup.__PAGE__.txt +3 -3
- package/out/setup/__next.setup.txt +3 -3
- package/out/setup.html +1 -1
- package/out/setup.txt +12 -12
- package/package.json +1 -1
- package/server/index.js +26 -0
- package/server/queue-watcher.js +47 -10
- package/server/queue-watcher.test.js +64 -0
- package/server/routes.batchProgress.test.js +94 -0
- package/server/routes.js +388 -23
- package/server/routes.parseActiveBatch.test.js +88 -0
- package/server/routes.telegramBridge.test.js +70 -0
- package/templates/CLAUDE.md +0 -1
- package/out/_next/static/chunks/006g3lco-9xqf.js +0 -1
- package/out/_next/static/chunks/035rt-n0oid7d.js +0 -1
- package/out/_next/static/chunks/0u~7e4fgf-u06.css +0 -2
- /package/out/_next/static/{6uvV3nUfwr_t_JKrZJSP8 → OzDK1Fplm2eUu23bzILlU}/_buildManifest.js +0 -0
- /package/out/_next/static/{6uvV3nUfwr_t_JKrZJSP8 → OzDK1Fplm2eUu23bzILlU}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{6uvV3nUfwr_t_JKrZJSP8 → OzDK1Fplm2eUu23bzILlU}/_ssgManifest.js +0 -0
package/out/setup.txt
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
1:"$Sreact.fragment"
|
|
2
|
-
2:I[34852,["/_next/static/chunks/
|
|
3
|
-
3:I[86081,["/_next/static/chunks/
|
|
4
|
-
4:I[12527,["/_next/static/chunks/
|
|
5
|
-
5:I[59763,["/_next/static/chunks/
|
|
6
|
-
6:I[64618,["/_next/static/chunks/
|
|
7
|
-
7:I[11717,["/_next/static/chunks/
|
|
2
|
+
2:I[34852,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"default"]
|
|
3
|
+
3:I[86081,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"default"]
|
|
4
|
+
4:I[12527,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"default"]
|
|
5
|
+
5:I[59763,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"default"]
|
|
6
|
+
6:I[64618,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js","/_next/static/chunks/084lff9v4p_vh.js"],"default"]
|
|
7
|
+
7:I[11717,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"OutletBoundary"]
|
|
8
8
|
8:"$Sreact.suspense"
|
|
9
|
-
b:I[11717,["/_next/static/chunks/
|
|
10
|
-
d:I[11717,["/_next/static/chunks/
|
|
11
|
-
f:I[92243,["/_next/static/chunks/
|
|
12
|
-
:HL["/_next/static/chunks/
|
|
9
|
+
b:I[11717,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"ViewportBoundary"]
|
|
10
|
+
d:I[11717,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"MetadataBoundary"]
|
|
11
|
+
f:I[92243,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"default",1]
|
|
12
|
+
:HL["/_next/static/chunks/05.po0c1knrbu.css","style"]
|
|
13
13
|
:HL["/_next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
|
14
|
-
0:{"P":null,"c":["","setup"],"q":"","i":false,"f":[[["",{"children":["setup",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/
|
|
14
|
+
0:{"P":null,"c":["","setup"],"q":"","i":false,"f":[[["",{"children":["setup",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/05.po0c1knrbu.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0za4cvk8.n0-y.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/_next/static/chunks/0ox7p_szjhn69.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"geist_mono_8d43a2aa-module__8Li5zG__variable h-full","children":["$","body",null,{"className":"h-full flex flex-col","children":[["$","$L2",null,{}],["$","div",null,{"className":"flex flex-1 min-h-0","children":[["$","$L3",null,{}],["$","main",null,{"className":"flex-1 min-w-0 overflow-auto","children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]]}]]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$L6",null,{}],[["$","script","script-0",{"src":"/_next/static/chunks/084lff9v4p_vh.js","async":true,"nonce":"$undefined"}]],["$","$L7",null,{"children":["$","$8",null,{"name":"Next.MetadataOutlet","children":"$@9"}]}]]}],{},null,false,null]},null,false,"$@a"]},null,false,null],["$","$1","h",{"children":[null,["$","$Lb",null,{"children":"$Lc"}],["$","div",null,{"hidden":true,"children":["$","$Ld",null,{"children":["$","$8",null,{"name":"Next.Metadata","children":"$Le"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$f",[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/05.po0c1knrbu.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"OzDK1Fplm2eUu23bzILlU"}
|
|
15
15
|
10:[]
|
|
16
16
|
a:"$W10"
|
|
17
17
|
c:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
|
18
|
-
11:I[80070,["/_next/static/chunks/
|
|
18
|
+
11:I[80070,["/_next/static/chunks/0za4cvk8.n0-y.js","/_next/static/chunks/0ox7p_szjhn69.js"],"IconMark"]
|
|
19
19
|
9:null
|
|
20
20
|
e:[["$","title","0",{"children":"QuadWork"}],["$","meta","1",{"name":"description","content":"Unified dashboard for multi-agent coding teams"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L11","3",{}]]
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -312,6 +312,32 @@ async function buildAgentArgs(projectId, agentId) {
|
|
|
312
312
|
if (flags) args.push(...flags);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
// #343: per-agent model + reasoning effort overrides. Persist in
|
|
316
|
+
// project.agents[agentId].{model,reasoning_effort} via the
|
|
317
|
+
// dashboard Agent Models widget. When unset, fall back to the
|
|
318
|
+
// CLI's own default so existing projects without overrides keep
|
|
319
|
+
// their current behavior.
|
|
320
|
+
//
|
|
321
|
+
// Codex: -c model="<slug>" / -c model_reasoning_effort="<level>"
|
|
322
|
+
// reasoning levels: minimal | low | medium | high (xhigh is
|
|
323
|
+
// deliberately NOT offered — it's the capacity-failure hot
|
|
324
|
+
// spot #343 was filed for).
|
|
325
|
+
// Claude: --model <slug>
|
|
326
|
+
// reasoning_effort is not wired for Claude — Anthropic's CLI
|
|
327
|
+
// doesn't expose an equivalent flag.
|
|
328
|
+
if (cliBase === "codex") {
|
|
329
|
+
if (agentCfg.model && typeof agentCfg.model === "string") {
|
|
330
|
+
args.push("-c", `model="${agentCfg.model}"`);
|
|
331
|
+
}
|
|
332
|
+
if (agentCfg.reasoning_effort && typeof agentCfg.reasoning_effort === "string") {
|
|
333
|
+
args.push("-c", `model_reasoning_effort="${agentCfg.reasoning_effort}"`);
|
|
334
|
+
}
|
|
335
|
+
} else if (cliBase === "claude") {
|
|
336
|
+
if (agentCfg.model && typeof agentCfg.model === "string") {
|
|
337
|
+
args.push("--model", agentCfg.model);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
315
341
|
// MCP config injection
|
|
316
342
|
const mcpHttpPort = project.mcp_http_port;
|
|
317
343
|
const token = project.agentchattr_token;
|
package/server/queue-watcher.js
CHANGED
|
@@ -11,8 +11,19 @@
|
|
|
11
11
|
* Reference: /Users/cho/Projects/agentchattr/wrapper.py lines 438-541
|
|
12
12
|
* (`_queue_watcher`). Polling (not fs.watch) is intentional: matches
|
|
13
13
|
* wrapper.py's behavior and avoids the cross-platform fs.watch
|
|
14
|
-
* footguns.
|
|
15
|
-
*
|
|
14
|
+
* footguns.
|
|
15
|
+
*
|
|
16
|
+
* #342 / quadwork#342: the v1 prompt intentionally omitted the
|
|
17
|
+
* identity hints from wrapper.py lines 501-528, which broke
|
|
18
|
+
* Claude Code agent sessions. Claude's default self-concept is
|
|
19
|
+
* `@claude`, so a bare `mcp read #general - you were mentioned`
|
|
20
|
+
* causes chat_read(sender: "claude") and a filter on `@claude`
|
|
21
|
+
* mentions — both wrong when the agent is actually @dev /
|
|
22
|
+
* @reviewerN. Codex doesn't trip the same way because its init
|
|
23
|
+
* path already claims identity. The fix here is to scope the
|
|
24
|
+
* wrapper.py additions to identity only: the injected prompt
|
|
25
|
+
* now explicitly names the agent slug and tells the agent which
|
|
26
|
+
* sender to use on chat_read and which mentions to look for.
|
|
16
27
|
*/
|
|
17
28
|
|
|
18
29
|
const fs = require("fs");
|
|
@@ -20,6 +31,38 @@ const path = require("path");
|
|
|
20
31
|
|
|
21
32
|
const POLL_INTERVAL_MS = 1000;
|
|
22
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Pure helper: build the injected prompt text for a given agent
|
|
36
|
+
* slug + trigger shape. Exported so it can be unit-tested without
|
|
37
|
+
* a PTY or a filesystem queue. Priority matches the tick() call
|
|
38
|
+
* site below: customPrompt > jobId > channel.
|
|
39
|
+
*
|
|
40
|
+
* agentName is expected to be the registered agent slug such as
|
|
41
|
+
* `dev`, `head`, `reviewer1`, `reviewer2`. The helper does not
|
|
42
|
+
* validate — upstream already controls who may register.
|
|
43
|
+
*/
|
|
44
|
+
function buildInjectionPrompt(agentName, { channel, jobId, customPrompt } = {}) {
|
|
45
|
+
if (customPrompt && typeof customPrompt === "string" && customPrompt.trim()) {
|
|
46
|
+
// Operator-supplied prompts already control the identity
|
|
47
|
+
// wording; leave them alone.
|
|
48
|
+
return customPrompt.trim();
|
|
49
|
+
}
|
|
50
|
+
if (jobId) {
|
|
51
|
+
return (
|
|
52
|
+
`You are @${agentName} in this AgentChattr instance. ` +
|
|
53
|
+
`mcp read job_id=${jobId} with sender: "${agentName}" — ` +
|
|
54
|
+
`you (@${agentName}) were mentioned in a job thread, take appropriate action.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const ch = channel || "general";
|
|
58
|
+
return (
|
|
59
|
+
`You are @${agentName} in this AgentChattr instance. ` +
|
|
60
|
+
`mcp read #${ch} with sender: "${agentName}" — ` +
|
|
61
|
+
`look for @${agentName} mentions (NOT @claude). ` +
|
|
62
|
+
`You were mentioned, take appropriate action.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
23
66
|
/**
|
|
24
67
|
* Start polling `{dataDir}/{agentName}_queue.jsonl`. When non-empty,
|
|
25
68
|
* read all lines, truncate the file (atomic-ish claim — same race the
|
|
@@ -78,14 +121,7 @@ function startQueueWatcher(dataDir, agentName, ptyTerm) {
|
|
|
78
121
|
}
|
|
79
122
|
if (!hasTrigger) return;
|
|
80
123
|
|
|
81
|
-
|
|
82
|
-
if (customPrompt) {
|
|
83
|
-
prompt = customPrompt;
|
|
84
|
-
} else if (jobId) {
|
|
85
|
-
prompt = `mcp read job_id=${jobId} - you were mentioned in a job thread, take appropriate action`;
|
|
86
|
-
} else {
|
|
87
|
-
prompt = `mcp read #${channel} - you were mentioned, take appropriate action`;
|
|
88
|
-
}
|
|
124
|
+
const prompt = buildInjectionPrompt(agentName, { channel, jobId, customPrompt });
|
|
89
125
|
|
|
90
126
|
// Flatten newlines: multi-line writes trigger paste detection in
|
|
91
127
|
// Claude Code (shows "[Pasted text +N]") and can break injection
|
|
@@ -122,4 +158,5 @@ function stopQueueWatcher(handle) {
|
|
|
122
158
|
module.exports = {
|
|
123
159
|
startQueueWatcher,
|
|
124
160
|
stopQueueWatcher,
|
|
161
|
+
buildInjectionPrompt,
|
|
125
162
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// #342 / quadwork#342: unit test for the identity-aware prompt
|
|
2
|
+
// builder. No test runner is wired up in this repo, so this file
|
|
3
|
+
// is a plain node:assert script — run it with `node server/queue-watcher.test.js`
|
|
4
|
+
// and it exits non-zero on any failure.
|
|
5
|
+
|
|
6
|
+
const assert = require("node:assert/strict");
|
|
7
|
+
const { buildInjectionPrompt } = require("./queue-watcher");
|
|
8
|
+
|
|
9
|
+
const DEFAULT_AGENT_SLUGS = ["dev", "head", "reviewer1", "reviewer2"];
|
|
10
|
+
|
|
11
|
+
// 1) Channel prompt — each of the 4 default slugs must:
|
|
12
|
+
// - name the agent with @<slug>
|
|
13
|
+
// - pass sender: "<slug>" explicitly
|
|
14
|
+
// - tell the agent to look for @<slug> mentions (NOT @claude)
|
|
15
|
+
for (const slug of DEFAULT_AGENT_SLUGS) {
|
|
16
|
+
const p = buildInjectionPrompt(slug, { channel: "general" });
|
|
17
|
+
assert.match(p, new RegExp(`You are @${slug} `), `channel: names @${slug}`);
|
|
18
|
+
assert.match(p, new RegExp(`sender: "${slug}"`), `channel: sender string for ${slug}`);
|
|
19
|
+
assert.match(p, new RegExp(`@${slug} mentions`), `channel: @${slug} mention filter`);
|
|
20
|
+
assert.match(p, /NOT @claude/, `channel: explicit NOT @claude guard for ${slug}`);
|
|
21
|
+
assert.match(p, /#general/, `channel: channel name for ${slug}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2) Channel defaults to "general" when not provided.
|
|
25
|
+
{
|
|
26
|
+
const p = buildInjectionPrompt("dev", {});
|
|
27
|
+
assert.match(p, /#general/, "channel defaults to general");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3) Non-default channel is passed through.
|
|
31
|
+
{
|
|
32
|
+
const p = buildInjectionPrompt("dev", { channel: "batch-33" });
|
|
33
|
+
assert.match(p, /#batch-33/, "custom channel is used");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 4) Job-thread prompt — each slug must:
|
|
37
|
+
// - name the agent with @<slug>
|
|
38
|
+
// - reference the job_id
|
|
39
|
+
// - pass sender: "<slug>" explicitly
|
|
40
|
+
for (const slug of DEFAULT_AGENT_SLUGS) {
|
|
41
|
+
const p = buildInjectionPrompt(slug, { jobId: "42" });
|
|
42
|
+
assert.match(p, new RegExp(`You are @${slug} `), `job: names @${slug}`);
|
|
43
|
+
assert.match(p, /job_id=42/, `job: job_id for ${slug}`);
|
|
44
|
+
assert.match(p, new RegExp(`sender: "${slug}"`), `job: sender string for ${slug}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 5) customPrompt wins over channel and jobId and is returned as-is.
|
|
48
|
+
{
|
|
49
|
+
const p = buildInjectionPrompt("dev", {
|
|
50
|
+
channel: "general",
|
|
51
|
+
jobId: "99",
|
|
52
|
+
customPrompt: " do the thing ",
|
|
53
|
+
});
|
|
54
|
+
assert.equal(p, "do the thing", "customPrompt overrides + trims");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 6) Blank customPrompt is ignored (falls through to channel/job path).
|
|
58
|
+
{
|
|
59
|
+
const p = buildInjectionPrompt("reviewer2", { customPrompt: " ", channel: "general" });
|
|
60
|
+
assert.match(p, /You are @reviewer2 /);
|
|
61
|
+
assert.match(p, /#general/);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`queue-watcher.test.js: all assertions passed (${DEFAULT_AGENT_SLUGS.length * 2 + 4} cases)`);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// #350 / quadwork#350: batch-progress no-linked-PR row builder +
|
|
2
|
+
// summarizer tests. Plain node:assert script — run with
|
|
3
|
+
// `node server/routes.batchProgress.test.js`.
|
|
4
|
+
|
|
5
|
+
const assert = require("node:assert/strict");
|
|
6
|
+
const { buildNoPrRow, summarizeItems } = require("./routes");
|
|
7
|
+
|
|
8
|
+
// 1) #350 regression fixture: CLOSED issue with no linked PR
|
|
9
|
+
// must render as 100% complete, not 0% queued.
|
|
10
|
+
{
|
|
11
|
+
const issue = {
|
|
12
|
+
number: 336,
|
|
13
|
+
title: "superseded by #338",
|
|
14
|
+
state: "CLOSED",
|
|
15
|
+
url: "https://github.com/realproject7/quadwork/issues/336",
|
|
16
|
+
};
|
|
17
|
+
const row = buildNoPrRow(issue);
|
|
18
|
+
assert.equal(row.status, "closed", "CLOSED with no PR → status=closed");
|
|
19
|
+
assert.equal(row.progress, 100, "CLOSED with no PR → 100%");
|
|
20
|
+
assert.match(row.label, /Closed.*✓/, "label has Closed and ✓ marker");
|
|
21
|
+
assert.equal(row.issue_number, 336);
|
|
22
|
+
assert.equal(row.url, issue.url);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2) OPEN issue with no linked PR still renders as queued.
|
|
26
|
+
{
|
|
27
|
+
const issue = {
|
|
28
|
+
number: 400,
|
|
29
|
+
title: "still open",
|
|
30
|
+
state: "OPEN",
|
|
31
|
+
url: "https://github.com/realproject7/quadwork/issues/400",
|
|
32
|
+
};
|
|
33
|
+
const row = buildNoPrRow(issue);
|
|
34
|
+
assert.equal(row.status, "queued", "OPEN with no PR → queued");
|
|
35
|
+
assert.equal(row.progress, 0);
|
|
36
|
+
assert.equal(row.label, "Issue · queued");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3) summarizeItems with a mix of merged and closed-without-PR:
|
|
40
|
+
// should count both toward the complete total, label "complete"
|
|
41
|
+
// when closed > 0.
|
|
42
|
+
{
|
|
43
|
+
const items = [
|
|
44
|
+
{ status: "merged" },
|
|
45
|
+
{ status: "merged" },
|
|
46
|
+
{ status: "merged" },
|
|
47
|
+
{ status: "merged" },
|
|
48
|
+
{ status: "merged" },
|
|
49
|
+
{ status: "closed" },
|
|
50
|
+
{ status: "closed" },
|
|
51
|
+
];
|
|
52
|
+
const out = summarizeItems(items);
|
|
53
|
+
assert.equal(out, "7/7 complete", "mixed merged+closed → X/N complete");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 4) summarizeItems with only merged items keeps the classic
|
|
57
|
+
// "X/N merged" wording (no behavior change for PR-only batches).
|
|
58
|
+
{
|
|
59
|
+
const items = [
|
|
60
|
+
{ status: "merged" },
|
|
61
|
+
{ status: "merged" },
|
|
62
|
+
{ status: "merged" },
|
|
63
|
+
];
|
|
64
|
+
assert.equal(summarizeItems(items), "3/3 merged");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 5) summarizeItems with a queued + closed mix: done count is
|
|
68
|
+
// closed only, queued surfaces in the detail tail.
|
|
69
|
+
{
|
|
70
|
+
const items = [
|
|
71
|
+
{ status: "closed" },
|
|
72
|
+
{ status: "queued" },
|
|
73
|
+
{ status: "queued" },
|
|
74
|
+
];
|
|
75
|
+
assert.equal(summarizeItems(items), "1/3 complete · 2 queued");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 6) summarizeItems with in-flight PR states still tallies them
|
|
79
|
+
// in the detail tail and keeps the done count at merged-only.
|
|
80
|
+
{
|
|
81
|
+
const items = [
|
|
82
|
+
{ status: "merged" },
|
|
83
|
+
{ status: "ready" },
|
|
84
|
+
{ status: "approved1" },
|
|
85
|
+
{ status: "in_review" },
|
|
86
|
+
{ status: "queued" },
|
|
87
|
+
];
|
|
88
|
+
assert.equal(
|
|
89
|
+
summarizeItems(items),
|
|
90
|
+
"1/5 merged · 1 ready to merge · 1 needs 2nd approval · 1 in review · 1 queued",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log("routes.batchProgress.test.js: all assertions passed (6 cases)");
|