quadwork 1.19.2 → 2.0.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/README.md +19 -35
- package/bin/quadwork.js +48 -1118
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +3 -3
- package/out/__next._full.txt +14 -14
- package/out/__next._head.txt +4 -4
- package/out/__next._index.txt +8 -8
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/{030cjkhts487t.js → 079wdniva~de1.js} +1 -1
- package/out/_next/static/chunks/{0n~dq4kpx9xxx.js → 07lhk_q6pmm3r.js} +1 -1
- package/out/_next/static/chunks/0_79hkefw1mo2.js +1 -0
- package/out/_next/static/chunks/{153f.fj8jlvle.js → 0_lyyn..t63bc.js} +1 -1
- package/out/_next/static/chunks/0oxv9vrvc17to.js +2 -0
- package/out/_next/static/chunks/0py7102i226n5.js +1 -0
- package/out/_next/static/chunks/{13fv-yi7.v52g.js → 0q4bm04c1jl_3.js} +1 -1
- package/out/_next/static/chunks/{0_idxioyl0p7h.js → 0sjhy6oe3mbon.js} +1 -1
- package/out/_next/static/chunks/13xk0vgfbrcld.css +2 -0
- package/out/_next/static/chunks/14k3bfe537f9_.js +25 -0
- package/out/_next/static/chunks/{turbopack-0qm-e3ifrz~2u.js → turbopack-0y2u-q0l2m67w.js} +1 -1
- package/out/_not-found/__next._full.txt +13 -13
- package/out/_not-found/__next._head.txt +4 -4
- package/out/_not-found/__next._index.txt +8 -8
- 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 +13 -13
- package/out/app-shell/__next._full.txt +13 -13
- package/out/app-shell/__next._head.txt +4 -4
- package/out/app-shell/__next._index.txt +8 -8
- 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 +13 -13
- package/out/index.html +1 -1
- package/out/index.txt +14 -14
- package/out/project/_/__next._full.txt +14 -14
- package/out/project/_/__next._head.txt +4 -4
- package/out/project/_/__next._index.txt +8 -8
- 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/_/queue/__next._full.txt +14 -14
- package/out/project/_/queue/__next._head.txt +4 -4
- package/out/project/_/queue/__next._index.txt +8 -8
- 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 +14 -14
- package/out/project/_.html +1 -1
- package/out/project/_.txt +14 -14
- package/out/settings/__next._full.txt +14 -14
- package/out/settings/__next._head.txt +4 -4
- package/out/settings/__next._index.txt +8 -8
- 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 +14 -14
- package/out/setup/__next._full.txt +14 -14
- package/out/setup/__next._head.txt +4 -4
- package/out/setup/__next._index.txt +8 -8
- 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 +14 -14
- package/package.json +4 -2
- package/server/ac-restore.js +128 -0
- package/server/bridges/discord.js +183 -0
- package/server/bridges/telegram.js +210 -0
- package/server/config.js +4 -60
- package/server/file-chat.js +318 -0
- package/server/index.js +173 -1286
- package/server/install-agentchattr.js +3 -284
- package/server/mcp-chat-shim.js +171 -0
- package/server/migrate-ac.js +158 -0
- package/server/pty-dispatcher.js +188 -0
- package/server/routes.js +149 -1397
- package/templates/CLAUDE.md +2 -2
- package/templates/OVERNIGHT-QUEUE.md +1 -1
- package/templates/seeds/butler.CLAUDE.md +30 -62
- package/templates/seeds/dev.AGENTS.md +10 -1
- package/templates/seeds/head.AGENTS.md +3 -3
- package/templates/seeds/re1.AGENTS.md +3 -3
- package/templates/seeds/re2.AGENTS.md +3 -3
- package/bridges/discord/__pycache__/discord_bridge.cpython-314.pyc +0 -0
- package/bridges/discord/discord_bridge.py +0 -666
- package/bridges/discord/requirements.txt +0 -2
- package/out/_next/static/chunks/0_bb~2.5h2ntm.css +0 -2
- package/out/_next/static/chunks/0makcdqkwobp6.js +0 -25
- package/out/_next/static/chunks/0uz5svjlo9dwl.js +0 -1
- package/out/_next/static/chunks/0zahstmgdrpy5.js +0 -1
- package/out/_next/static/chunks/0zfotsowwll1x.js +0 -2
- package/server/__tests__/bridge-auto-stop-guard.test.js +0 -134
- package/server/__tests__/rate-limit-handling.test.js +0 -168
- package/server/__tests__/scrub-secrets.test.js +0 -235
- package/server/__tests__/v1110-security-qa.test.js +0 -312
- package/server/agentchattr-registry.js +0 -188
- package/server/install-agentchattr.patchCrashTimeout.test.js +0 -71
- package/server/queue-watcher.js +0 -171
- package/server/queue-watcher.test.js +0 -64
- package/server/routes.batchProgress.test.js +0 -94
- package/server/routes.chatWsSend.test.js +0 -161
- package/server/routes.discordBridge.test.js +0 -80
- package/server/routes.parseActiveBatch.test.js +0 -88
- package/server/routes.telegramBridge.test.js +0 -241
- package/templates/config.toml +0 -72
- package/templates/wrapper.py +0 -70
- /package/out/_next/static/{K7A3YZrh4sLaRRP1-Lq7v → 479UD5Kit4YvCmtgO25VT}/_buildManifest.js +0 -0
- /package/out/_next/static/{K7A3YZrh4sLaRRP1-Lq7v → 479UD5Kit4YvCmtgO25VT}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{K7A3YZrh4sLaRRP1-Lq7v → 479UD5Kit4YvCmtgO25VT}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #730: PTY injection dispatcher — deliver chat messages to agents via
|
|
3
|
+
* terminal stdin when they are @mentioned. Primary delivery mechanism
|
|
4
|
+
* for file-chat mode; agents treat injected text as user prompts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const IDLE_THRESHOLD_MS = 5000;
|
|
8
|
+
const COALESCE_WINDOW_MS = 1000;
|
|
9
|
+
const ACTIVE_SUPPRESSION_MS = 30000;
|
|
10
|
+
|
|
11
|
+
// Per-agent coalescing timers: key = "project/agent" → timeout handle
|
|
12
|
+
const _coalesceTimers = new Map();
|
|
13
|
+
|
|
14
|
+
// Per-agent last chat send timestamp: key = "project/agent" → epoch ms
|
|
15
|
+
const _lastChatSentAt = new Map();
|
|
16
|
+
|
|
17
|
+
// Per-agent pending wake flag: key = "project/agent" → true
|
|
18
|
+
const _pendingWake = new Map();
|
|
19
|
+
|
|
20
|
+
// Per-agent drain listeners: key = "project/agent" → { disposable, timeoutHandle }
|
|
21
|
+
const _drainListeners = new Map();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Dispatch a chat message to mentioned agents' PTYs.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} projectId
|
|
27
|
+
* @param {object} msg - The appended message record (has .mentions, .sender, .text, .id, .type)
|
|
28
|
+
* @param {Map} agentSessions - The global agentSessions map
|
|
29
|
+
* @param {object} deps - { isLoopGuardPaused, safeWrite }
|
|
30
|
+
*/
|
|
31
|
+
function dispatchToAgentPTY(projectId, msg, agentSessions, deps) {
|
|
32
|
+
if (!msg || msg.type === "system") return;
|
|
33
|
+
if (deps.isLoopGuardPaused(projectId)) return;
|
|
34
|
+
if (!msg.mentions || msg.mentions.length === 0) return;
|
|
35
|
+
|
|
36
|
+
// Track when this sender last posted (for active-agent suppression)
|
|
37
|
+
const senderKey = `${projectId}/${msg.sender}`;
|
|
38
|
+
if (agentSessions.has(senderKey)) {
|
|
39
|
+
_lastChatSentAt.set(senderKey, Date.now());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const agentId of msg.mentions) {
|
|
43
|
+
const key = `${projectId}/${agentId}`;
|
|
44
|
+
const session = agentSessions.get(key);
|
|
45
|
+
if (!session || !session.term || session.state !== "running") continue;
|
|
46
|
+
if (msg.sender === agentId) continue;
|
|
47
|
+
|
|
48
|
+
// #736: skip injection if agent recently sent a message — they're
|
|
49
|
+
// already active and will read chat via chat_read
|
|
50
|
+
const lastSent = _lastChatSentAt.get(key);
|
|
51
|
+
if (lastSent && (Date.now() - lastSent < ACTIVE_SUPPRESSION_MS)) continue;
|
|
52
|
+
|
|
53
|
+
if (isAgentBusy(session)) {
|
|
54
|
+
queuePendingWake(key, session, deps);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
scheduleCoalescedInjection(key, projectId, agentId, msg, agentSessions, deps);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isAgentBusy(session) {
|
|
63
|
+
return session.lastOutputAt && (Date.now() - session.lastOutputAt < IDLE_THRESHOLD_MS);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Queue a pending wake using high-water mark (since_id).
|
|
68
|
+
* Hooks term.onData to drain after idle period, with a fallback
|
|
69
|
+
* timer so the wake fires even if no further PTY output arrives.
|
|
70
|
+
*/
|
|
71
|
+
function queuePendingWake(key, session, deps) {
|
|
72
|
+
_pendingWake.set(key, true);
|
|
73
|
+
|
|
74
|
+
const drainFn = () => {
|
|
75
|
+
if (!_pendingWake.get(key)) {
|
|
76
|
+
cleanupDrainListener(key);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
_pendingWake.delete(key);
|
|
80
|
+
cleanupDrainListener(key);
|
|
81
|
+
|
|
82
|
+
const lastSent = _lastChatSentAt.get(key);
|
|
83
|
+
if (lastSent && (Date.now() - lastSent < ACTIVE_SUPPRESSION_MS)) return;
|
|
84
|
+
|
|
85
|
+
injectIntoTerm(session.term, buildInjectionPrompt(session.agentId), deps);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (_drainListeners.has(key)) return;
|
|
89
|
+
|
|
90
|
+
const state = { timeoutHandle: null };
|
|
91
|
+
|
|
92
|
+
function resetIdleTimer() {
|
|
93
|
+
if (state.timeoutHandle) clearTimeout(state.timeoutHandle);
|
|
94
|
+
state.timeoutHandle = setTimeout(drainFn, IDLE_THRESHOLD_MS);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Start fallback timer immediately so drain fires even without further output
|
|
98
|
+
resetIdleTimer();
|
|
99
|
+
|
|
100
|
+
const disposable = session.term.onData(() => {
|
|
101
|
+
resetIdleTimer();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
_drainListeners.set(key, { disposable, state });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function cleanupDrainListener(key) {
|
|
108
|
+
const listener = _drainListeners.get(key);
|
|
109
|
+
if (!listener) return;
|
|
110
|
+
if (listener.disposable && typeof listener.disposable.dispose === "function") {
|
|
111
|
+
listener.disposable.dispose();
|
|
112
|
+
}
|
|
113
|
+
if (listener.state && listener.state.timeoutHandle) {
|
|
114
|
+
clearTimeout(listener.state.timeoutHandle);
|
|
115
|
+
}
|
|
116
|
+
_drainListeners.delete(key);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildInjectionPrompt(agentId) {
|
|
120
|
+
return (
|
|
121
|
+
`You are @${agentId}. New messages may be addressed to you in the project chat. ` +
|
|
122
|
+
`Call the chat_read MCP tool to read recent messages. ` +
|
|
123
|
+
`Act only on messages that explicitly mention @${agentId}. ` +
|
|
124
|
+
`Ignore messages addressed to other agents.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Coalesce burst mentions within a 1s window per agent.
|
|
130
|
+
*/
|
|
131
|
+
function scheduleCoalescedInjection(key, projectId, agentId, msg, agentSessions, deps) {
|
|
132
|
+
const existing = _coalesceTimers.get(key);
|
|
133
|
+
if (existing) {
|
|
134
|
+
existing.messages.push(msg);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const state = { messages: [msg] };
|
|
139
|
+
const timer = setTimeout(() => {
|
|
140
|
+
_coalesceTimers.delete(key);
|
|
141
|
+
const session = agentSessions.get(key);
|
|
142
|
+
if (!session || !session.term || session.state !== "running") return;
|
|
143
|
+
|
|
144
|
+
const lastSent = _lastChatSentAt.get(key);
|
|
145
|
+
if (lastSent && (Date.now() - lastSent < ACTIVE_SUPPRESSION_MS)) return;
|
|
146
|
+
|
|
147
|
+
injectIntoTerm(session.term, buildInjectionPrompt(agentId), deps);
|
|
148
|
+
}, COALESCE_WINDOW_MS);
|
|
149
|
+
|
|
150
|
+
state.timer = timer;
|
|
151
|
+
_coalesceTimers.set(key, state);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function injectIntoTerm(term, text, deps) {
|
|
155
|
+
const flat = text.replace(/\n/g, " ");
|
|
156
|
+
deps.safeWrite(term, flat);
|
|
157
|
+
const submitDelayMs = Math.max(300, flat.length);
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
try { deps.safeWrite(term, "\r"); } catch {}
|
|
160
|
+
}, submitDelayMs);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Cleanup all timers for a session (call on stop/exit).
|
|
165
|
+
*/
|
|
166
|
+
function cleanupSession(key) {
|
|
167
|
+
const coalesce = _coalesceTimers.get(key);
|
|
168
|
+
if (coalesce) {
|
|
169
|
+
clearTimeout(coalesce.timer);
|
|
170
|
+
_coalesceTimers.delete(key);
|
|
171
|
+
}
|
|
172
|
+
_pendingWake.delete(key);
|
|
173
|
+
_lastChatSentAt.delete(key);
|
|
174
|
+
cleanupDrainListener(key);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
dispatchToAgentPTY,
|
|
179
|
+
cleanupSession,
|
|
180
|
+
// Exported for testing
|
|
181
|
+
_coalesceTimers,
|
|
182
|
+
_pendingWake,
|
|
183
|
+
_drainListeners,
|
|
184
|
+
_lastChatSentAt,
|
|
185
|
+
IDLE_THRESHOLD_MS,
|
|
186
|
+
COALESCE_WINDOW_MS,
|
|
187
|
+
ACTIVE_SUPPRESSION_MS,
|
|
188
|
+
};
|