ptbk 0.119.0-16 → 0.119.0-21
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 +27 -9
- package/dist/book/commitments.d.ts +38 -10
- package/dist/book/commitments.js +61 -17
- package/dist/book/commitments.js.map +1 -1
- package/dist/book/convert.d.ts +36 -0
- package/dist/book/convert.js +156 -0
- package/dist/book/convert.js.map +1 -0
- package/dist/book/parse.d.ts +50 -13
- package/dist/book/parse.js +132 -45
- package/dist/book/parse.js.map +1 -1
- package/dist/book/references.d.ts +12 -4
- package/dist/book/references.js +34 -9
- package/dist/book/references.js.map +1 -1
- package/dist/book/write.d.ts +42 -0
- package/dist/book/write.js +74 -0
- package/dist/book/write.js.map +1 -0
- package/dist/browser/browser.d.ts +122 -12
- package/dist/browser/browser.js +372 -21
- package/dist/browser/browser.js.map +1 -1
- package/dist/browser/recording.d.ts +3 -3
- package/dist/browser/recording.js +5 -5
- package/dist/browser/recording.js.map +1 -1
- package/dist/chats/chat-book.d.ts +105 -0
- package/dist/chats/chat-book.js +180 -0
- package/dist/chats/chat-book.js.map +1 -0
- package/dist/chats/conversation.d.ts +54 -0
- package/dist/chats/conversation.js +188 -0
- package/dist/chats/conversation.js.map +1 -0
- package/dist/chats/local.d.ts +46 -0
- package/dist/chats/local.js +184 -0
- package/dist/chats/local.js.map +1 -0
- package/dist/chats/migration.d.ts +12 -0
- package/dist/chats/migration.js +62 -0
- package/dist/chats/migration.js.map +1 -0
- package/dist/chats/service.d.ts +81 -0
- package/dist/chats/service.js +122 -0
- package/dist/chats/service.js.map +1 -0
- package/dist/chats/store.d.ts +132 -0
- package/dist/chats/store.js +181 -0
- package/dist/chats/store.js.map +1 -0
- package/dist/chats/task-chats.d.ts +52 -0
- package/dist/chats/task-chats.js +96 -0
- package/dist/chats/task-chats.js.map +1 -0
- package/dist/cli/chats.d.ts +45 -0
- package/dist/cli/chats.js +108 -0
- package/dist/cli/chats.js.map +1 -0
- package/dist/cli/commands/add.js +32 -22
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/answer.d.ts +2 -1
- package/dist/cli/commands/answer.js +35 -41
- package/dist/cli/commands/answer.js.map +1 -1
- package/dist/cli/commands/ask.d.ts +7 -0
- package/dist/cli/commands/ask.js +37 -0
- package/dist/cli/commands/ask.js.map +1 -0
- package/dist/cli/commands/boilerplates.js +14 -3
- package/dist/cli/commands/boilerplates.js.map +1 -1
- package/dist/cli/commands/chat.d.ts +9 -0
- package/dist/cli/commands/chat.js +89 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/dashboard.d.ts +3 -1
- package/dist/cli/commands/dashboard.js +41 -3
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/find.js +6 -8
- package/dist/cli/commands/find.js.map +1 -1
- package/dist/cli/commands/fix.js +4 -6
- package/dist/cli/commands/fix.js.map +1 -1
- package/dist/cli/commands/init.d.ts +0 -1
- package/dist/cli/commands/init.js +38 -38
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/lint.js +16 -2
- package/dist/cli/commands/lint.js.map +1 -1
- package/dist/cli/commands/list.js +21 -13
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts +2 -1
- package/dist/cli/commands/migrate.js +70 -24
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/pause.d.ts +8 -0
- package/dist/cli/commands/pause.js +40 -0
- package/dist/cli/commands/pause.js.map +1 -0
- package/dist/cli/commands/plan.js +6 -9
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/recover.js +7 -3
- package/dist/cli/commands/recover.js.map +1 -1
- package/dist/cli/commands/restart.d.ts +3 -1
- package/dist/cli/commands/restart.js +74 -18
- package/dist/cli/commands/restart.js.map +1 -1
- package/dist/cli/commands/resume.d.ts +8 -0
- package/dist/cli/commands/resume.js +15 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/run.js +6 -9
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/start.js +40 -36
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts +2 -1
- package/dist/cli/commands/status.js +5 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/stop.d.ts +3 -1
- package/dist/cli/commands/stop.js +18 -8
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/commands/verify.js +7 -7
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/common.d.ts +13 -0
- package/dist/cli/common.js +22 -0
- package/dist/cli/common.js.map +1 -1
- package/dist/cli/engine-delegation.d.ts +14 -0
- package/dist/cli/engine-delegation.js +46 -0
- package/dist/cli/engine-delegation.js.map +1 -0
- package/dist/cli/lifecycle.d.ts +40 -0
- package/dist/cli/lifecycle.js +71 -0
- package/dist/cli/lifecycle.js.map +1 -0
- package/dist/cli/registry.js +113 -31
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/service-view.d.ts +14 -2
- package/dist/cli/service-view.js +46 -5
- package/dist/cli/service-view.js.map +1 -1
- package/dist/cli/sources.js +5 -8
- package/dist/cli/sources.js.map +1 -1
- package/dist/cli/spec.js +1 -1
- package/dist/cli/spec.js.map +1 -1
- package/dist/common/app-data.d.ts +9 -0
- package/dist/common/app-data.js +20 -0
- package/dist/common/app-data.js.map +1 -0
- package/dist/common/errors.d.ts +2 -0
- package/dist/common/errors.js.map +1 -1
- package/dist/common/time.d.ts +6 -0
- package/dist/common/time.js +9 -0
- package/dist/common/time.js.map +1 -1
- package/dist/engine/agenda.d.ts +213 -43
- package/dist/engine/agenda.js +846 -260
- package/dist/engine/agenda.js.map +1 -1
- package/dist/engine/executor.d.ts +23 -7
- package/dist/engine/executor.js +131 -84
- package/dist/engine/executor.js.map +1 -1
- package/dist/engine/generated.d.ts +21 -1
- package/dist/engine/generated.js +36 -21
- package/dist/engine/generated.js.map +1 -1
- package/dist/engine/preview.js +10 -10
- package/dist/engine/preview.js.map +1 -1
- package/dist/engine/prompt.d.ts +16 -4
- package/dist/engine/prompt.js +22 -4
- package/dist/engine/prompt.js.map +1 -1
- package/dist/engine/routing.js +28 -4
- package/dist/engine/routing.js.map +1 -1
- package/dist/engine/snapshot.d.ts +178 -29
- package/dist/engine/state.d.ts +40 -13
- package/dist/engine/state.js +20 -5
- package/dist/engine/state.js.map +1 -1
- package/dist/engine/task-trace.d.ts +35 -7
- package/dist/engine/task-trace.js +74 -15
- package/dist/engine/task-trace.js.map +1 -1
- package/dist/engine/team.d.ts +20 -7
- package/dist/engine/team.js +30 -24
- package/dist/engine/team.js.map +1 -1
- package/dist/engine/traces.d.ts +1 -1
- package/dist/engine/traces.js +1 -1
- package/dist/expert/_main.md +67 -52
- package/dist/expert/agents/_index.md +11 -13
- package/dist/expert/agents/chatter.md +31 -0
- package/dist/expert/agents/consistency.md +6 -11
- package/dist/expert/agents/core-agents.md +9 -7
- package/dist/expert/agents/expert.md +2 -2
- package/dist/expert/agents/fixer.md +24 -0
- package/dist/expert/agents/learning.md +1 -1
- package/dist/expert/agents/non-agents.md +6 -4
- package/dist/expert/agents/teacher.md +1 -1
- package/dist/expert/agents/visual-identity.md +4 -4
- package/dist/expert/book-language/_index.md +9 -8
- package/dist/expert/book-language/commitments/_index.md +23 -3
- package/dist/expert/book-language/commitments/agent.md +11 -0
- package/dist/expert/book-language/commitments/chat-member.md +7 -0
- package/dist/expert/book-language/commitments/chat-message.md +11 -0
- package/dist/expert/book-language/commitments/chat-status.md +7 -0
- package/dist/expert/book-language/commitments/chat.md +11 -0
- package/dist/expert/book-language/commitments/entity.md +9 -0
- package/dist/expert/book-language/commitments/from.md +1 -1
- package/dist/expert/book-language/commitments/goal.md +6 -2
- package/dist/expert/book-language/commitments/link.md +9 -0
- package/dist/expert/book-language/commitments/task-depends-on.md +1 -1
- package/dist/expert/book-language/commitments/task-parent.md +1 -1
- package/dist/expert/book-language/commitments/task.md +7 -3
- package/dist/expert/book-language/commitments/team.md +1 -1
- package/dist/expert/book-language/inheritance.md +2 -2
- package/dist/expert/book-language/references.md +20 -24
- package/dist/expert/book-language/syntax.md +54 -11
- package/dist/expert/cli/acceptance.md +59 -24
- package/dist/expert/cli/api.md +23 -9
- package/dist/expert/cli/authoring.md +6 -4
- package/dist/expert/cli/autonomous-operation.md +3 -3
- package/dist/expert/cli/browser.md +29 -18
- package/dist/expert/cli/channels.md +5 -7
- package/dist/expert/cli/chat-ui.md +32 -0
- package/dist/expert/cli/chats.md +59 -0
- package/dist/expert/cli/cli.md +44 -26
- package/dist/expert/cli/configuration.md +3 -3
- package/dist/expert/cli/controls.md +13 -7
- package/dist/expert/cli/dashboard-details.md +23 -0
- package/dist/expert/cli/dashboard-layout.md +19 -12
- package/dist/expert/cli/dashboard.md +12 -8
- package/dist/expert/cli/execution.md +8 -6
- package/dist/expert/cli/first-task.md +4 -4
- package/dist/expert/cli/generated-names.md +44 -0
- package/dist/expert/cli/git-persistence.md +19 -17
- package/dist/expert/cli/global-dashboard.md +33 -0
- package/dist/expert/cli/goal-discovery.md +1 -1
- package/dist/expert/cli/help.md +1 -1
- package/dist/expert/cli/initialization-ui.md +2 -0
- package/dist/expert/cli/initialization.md +9 -7
- package/dist/expert/cli/interactive-ui.md +9 -5
- package/dist/expert/cli/locking.md +21 -0
- package/dist/expert/cli/migration.md +22 -6
- package/dist/expert/cli/process-modes.md +21 -13
- package/dist/expert/cli/recovery.md +8 -6
- package/dist/expert/cli/recurrence.md +1 -1
- package/dist/expert/cli/rich-input.md +2 -2
- package/dist/expert/cli/service-lifecycle.md +57 -0
- package/dist/expert/cli/start.md +2 -2
- package/dist/expert/cli/status-view.md +16 -12
- package/dist/expert/cli/task-books.md +11 -15
- package/dist/expert/cli/task-identity.md +8 -10
- package/dist/expert/cli/task-markdown.md +6 -10
- package/dist/expert/cli/task-origins.md +3 -1
- package/dist/expert/cli/task-relationships.md +5 -9
- package/dist/expert/cli/team.md +17 -7
- package/dist/expert/cli/terminal.md +20 -12
- package/dist/expert/cli/trace-configuration.md +1 -1
- package/dist/expert/cli/traces.md +19 -23
- package/dist/expert/cli/user-interactions.md +14 -26
- package/dist/expert/cli/workspace.md +15 -9
- package/dist/expert/concepts/engine-and-project.md +11 -0
- package/dist/expert/concepts/project-model.md +39 -0
- package/dist/expert/dictionary.md +21 -15
- package/dist/harness/plugins/claude-code/index.js +1 -0
- package/dist/harness/plugins/claude-code/index.js.map +1 -1
- package/dist/harness/plugins/cline/index.js +1 -0
- package/dist/harness/plugins/cline/index.js.map +1 -1
- package/dist/harness/plugins/openai-codex/index.js +1 -0
- package/dist/harness/plugins/openai-codex/index.js.map +1 -1
- package/dist/harness/plugins/opencode/index.js +1 -0
- package/dist/harness/plugins/opencode/index.js.map +1 -1
- package/dist/harness/types.d.ts +3 -1
- package/dist/project/agents.d.ts +29 -81
- package/dist/project/agents.js +48 -267
- package/dist/project/agents.js.map +1 -1
- package/dist/project/books.d.ts +150 -0
- package/dist/project/books.js +253 -0
- package/dist/project/books.js.map +1 -0
- package/dist/project/config.js +2 -2
- package/dist/project/config.js.map +1 -1
- package/dist/project/defaults.d.ts +35 -6
- package/dist/project/defaults.js +73 -40
- package/dist/project/defaults.js.map +1 -1
- package/dist/project/expert.js +18 -10
- package/dist/project/expert.js.map +1 -1
- package/dist/project/git.d.ts +4 -3
- package/dist/project/git.js +16 -5
- package/dist/project/git.js.map +1 -1
- package/dist/project/setup.d.ts +4 -1
- package/dist/project/setup.js +26 -6
- package/dist/project/setup.js.map +1 -1
- package/dist/service/actions.d.ts +34 -10
- package/dist/service/actions.js +51 -19
- package/dist/service/actions.js.map +1 -1
- package/dist/service/control.d.ts +53 -1
- package/dist/service/control.js +64 -4
- package/dist/service/control.js.map +1 -1
- package/dist/service/discovery.d.ts +133 -0
- package/dist/service/discovery.js +138 -0
- package/dist/service/discovery.js.map +1 -0
- package/dist/service/host.d.ts +8 -1
- package/dist/service/host.js +65 -18
- package/dist/service/host.js.map +1 -1
- package/dist/service/http.d.ts +3 -0
- package/dist/service/http.js +26 -6
- package/dist/service/http.js.map +1 -1
- package/dist/service/launcher.d.ts +66 -13
- package/dist/service/launcher.js +78 -18
- package/dist/service/launcher.js.map +1 -1
- package/dist/service/records.d.ts +12 -2
- package/dist/service/records.js +19 -5
- package/dist/service/records.js.map +1 -1
- package/dist/service/spa/app.css +83 -2
- package/dist/service/spa/app.js +393 -72
- package/dist/service/supervisor.js +7 -1
- package/dist/service/supervisor.js.map +1 -1
- package/dist/service/worker.js +14 -5
- package/dist/service/worker.js.map +1 -1
- package/dist/tasks/authoring.d.ts +3 -5
- package/dist/tasks/authoring.js +6 -7
- package/dist/tasks/authoring.js.map +1 -1
- package/dist/tasks/book-task.d.ts +18 -10
- package/dist/tasks/book-task.js +27 -23
- package/dist/tasks/book-task.js.map +1 -1
- package/dist/tasks/eligibility.d.ts +15 -7
- package/dist/tasks/eligibility.js +41 -14
- package/dist/tasks/eligibility.js.map +1 -1
- package/dist/tasks/identity.d.ts +41 -15
- package/dist/tasks/identity.js +100 -31
- package/dist/tasks/identity.js.map +1 -1
- package/dist/tasks/markdown.d.ts +7 -14
- package/dist/tasks/markdown.js +15 -29
- package/dist/tasks/markdown.js.map +1 -1
- package/dist/tasks/model.d.ts +20 -4
- package/dist/tasks/model.js +17 -0
- package/dist/tasks/model.js.map +1 -1
- package/dist/tasks/sources.d.ts +7 -2
- package/dist/tasks/sources.js +46 -35
- package/dist/tasks/sources.js.map +1 -1
- package/dist/ui/chat-client.d.ts +70 -0
- package/dist/ui/chat-client.js +209 -0
- package/dist/ui/chat-client.js.map +1 -0
- package/dist/ui/dashboard.d.ts +8 -5
- package/dist/ui/dashboard.js +227 -86
- package/dist/ui/dashboard.js.map +1 -1
- package/dist/ui/details.d.ts +64 -0
- package/dist/ui/details.js +175 -0
- package/dist/ui/details.js.map +1 -0
- package/dist/ui/global-dashboard.d.ts +59 -0
- package/dist/ui/global-dashboard.js +307 -0
- package/dist/ui/global-dashboard.js.map +1 -0
- package/dist/ui/status-view.d.ts +7 -0
- package/dist/ui/status-view.js +94 -36
- package/dist/ui/status-view.js.map +1 -1
- package/package.json +1 -1
- package/dist/engine/requests.d.ts +0 -111
- package/dist/engine/requests.js +0 -173
- package/dist/engine/requests.js.map +0 -1
- package/dist/expert/book-language/commitments/task-id.md +0 -9
- package/dist/tasks/prepare.d.ts +0 -33
- package/dist/tasks/prepare.js +0 -90
- package/dist/tasks/prepare.js.map +0 -1
package/dist/service/spa/app.js
CHANGED
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
busy: new Set(),
|
|
20
20
|
qrFor: null,
|
|
21
21
|
frame: 0,
|
|
22
|
+
/** Open chat: its name, loaded messages and the message count they reflect. */
|
|
23
|
+
chat: null,
|
|
24
|
+
/** Agents offered for a new chat (loaded on demand). */
|
|
25
|
+
agents: null,
|
|
26
|
+
/** New-chat entry: 'entry' (Chat with Chatter / Choose another agent) or 'all' (every valid alternative). */
|
|
27
|
+
newChat: false,
|
|
28
|
+
showAllChats: false,
|
|
29
|
+
/** Viewer-local detail panels (initially collapsed; never engine state). */
|
|
30
|
+
details: { execution: false, browser: false },
|
|
22
31
|
};
|
|
23
32
|
|
|
24
33
|
/** Creates a DOM element; text children are always inserted as text (never parsed as HTML). */
|
|
@@ -95,80 +104,303 @@
|
|
|
95
104
|
return h('span', { className: `pill ${tone || ''}` }, text);
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
/** Runs
|
|
99
|
-
async function control(action, confirmText) {
|
|
107
|
+
/** Runs one of the engine's shared control operations through the API and reports its real outcome. */
|
|
108
|
+
async function control(action, confirmText, extra) {
|
|
100
109
|
if (confirmText && !window.confirm(confirmText)) return;
|
|
110
|
+
const key = `control:${action}`;
|
|
111
|
+
if (state.busy.has(key)) return;
|
|
112
|
+
state.busy.add(key);
|
|
101
113
|
try {
|
|
102
|
-
const result = await api('/api/control', { method: 'POST', body: { action } });
|
|
114
|
+
const result = await api('/api/control', { method: 'POST', body: { action, ...(extra || {}) } });
|
|
103
115
|
toast(result.outcome);
|
|
104
116
|
} catch (error) {
|
|
105
|
-
toast(`
|
|
117
|
+
toast(`Not applied: ${error.message}`);
|
|
118
|
+
} finally {
|
|
119
|
+
state.busy.delete(key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Lifecycle actions applicable to the live service state (the same operations as ptbk pause/resume/stop/restart). */
|
|
124
|
+
function lifecycleActions(snapshot) {
|
|
125
|
+
const service = snapshot.service;
|
|
126
|
+
const lifecycle = service.lifecycle || {};
|
|
127
|
+
const stateName = service.state === 'paused' ? 'pause' : service.state;
|
|
128
|
+
if (stateName === 'stopped' || stateName === 'finished') return [];
|
|
129
|
+
const actions = [];
|
|
130
|
+
const restartable = lifecycle.restartable !== false && service.mode !== 'run';
|
|
131
|
+
const pending = lifecycle.intent === 'restart' ? 'restart' : 'stop';
|
|
132
|
+
if (stateName === 'running') actions.push({ action: 'pause', label: 'Pause' });
|
|
133
|
+
if (stateName === 'pause') actions.push({ action: 'resume', label: 'Resume', primary: true });
|
|
134
|
+
if (stateName === 'pausing') actions.push({ action: 'resume', label: 'Cancel pause' });
|
|
135
|
+
if (stateName === 'stopping' && lifecycle.cancellable) {
|
|
136
|
+
actions.push({ action: 'resume', label: `Cancel ${pending}`, primary: true });
|
|
137
|
+
actions.push({ action: 'pause', label: 'Pause instead', confirm: `Cancel the pending ${pending} and pause instead?`, extra: { replaceStop: true, revision: lifecycle.revision } });
|
|
138
|
+
}
|
|
139
|
+
if (stateName !== 'stopping' || (lifecycle.intent === 'restart' && lifecycle.cancellable)) {
|
|
140
|
+
actions.push({ action: 'stop', label: stateName === 'pausing' ? 'Stop instead' : 'Stop (graceful)', confirm: 'Stop the service after the current task settles? Every viewer shares this engine; closing this page does not stop it.' });
|
|
106
141
|
}
|
|
142
|
+
if (restartable && stateName !== 'stopping') actions.push({ action: 'restart', label: 'Restart', confirm: 'Restart the service? The current task finishes first; then exactly one replacement starts with the same configuration.' });
|
|
143
|
+
if (lifecycle.intent !== 'force-stop') actions.push({ action: 'force-stop', label: 'Force stop', danger: true, confirm: 'Force stop now? Owned work is interrupted immediately (recoverable, never marked done).' });
|
|
144
|
+
return actions;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** What the live service state means (never a fifth "stopped" live state). */
|
|
148
|
+
function lifecycleText(snapshot) {
|
|
149
|
+
const service = snapshot.service;
|
|
150
|
+
const lifecycle = service.lifecycle || {};
|
|
151
|
+
const task = snapshot.current ? `“${snapshot.current.taskTitle}”` : 'the current task';
|
|
152
|
+
switch (service.state === 'paused' ? 'pause' : service.state) {
|
|
153
|
+
case 'running':
|
|
154
|
+
return snapshot.current ? 'New tasks start when ready.' : 'Idle is normal; new tasks start when ready.';
|
|
155
|
+
case 'pausing':
|
|
156
|
+
return `${task} finishes first; then no task starts until you resume.`;
|
|
157
|
+
case 'pause':
|
|
158
|
+
return 'Alive and controllable; no task runs or starts.';
|
|
159
|
+
case 'stopping':
|
|
160
|
+
if (lifecycle.intent === 'force-stop') return 'Force stop: owned work is being interrupted (recoverable).';
|
|
161
|
+
if (lifecycle.shutdown) return lifecycle.intent === 'restart' ? 'Shutting down for a restart; one replacement starts next.' : 'Shutting down.';
|
|
162
|
+
return `${task} finishes first; then the service ${lifecycle.intent === 'restart' ? 'restarts (exactly one replacement)' : 'exits'}.`;
|
|
163
|
+
case 'stopped':
|
|
164
|
+
return 'No engine is running (an observation, not a live state). Start it with ptbk start.';
|
|
165
|
+
default:
|
|
166
|
+
return 'The finite run ended.';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Loads (or reloads) the open chat; opening marks it read but never answers anything. */
|
|
171
|
+
async function loadChat(name) {
|
|
172
|
+
try {
|
|
173
|
+
const detail = await api(`/api/chats/${encodeURIComponent(name)}`);
|
|
174
|
+
const changed = !state.chat || state.chat.name !== detail.summary.name || state.chat.detail?.messages.length !== detail.messages.length || state.chat.detail?.summary.status !== detail.summary.status;
|
|
175
|
+
state.chat = { name: detail.summary.name, detail };
|
|
176
|
+
if (changed) await api(`/api/chats/${encodeURIComponent(detail.summary.name)}/read`, { method: 'POST' }).catch(() => undefined);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
toast(`Chat unavailable: ${error.message}`);
|
|
179
|
+
state.chat = null;
|
|
180
|
+
}
|
|
181
|
+
render(state.snapshot);
|
|
107
182
|
}
|
|
108
183
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
state.
|
|
184
|
+
function openChat(name) {
|
|
185
|
+
state.newChat = false;
|
|
186
|
+
state.chat = { name, detail: null };
|
|
187
|
+
render(state.snapshot);
|
|
188
|
+
loadChat(name);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Sends one message (an answer is an ordinary committed chat message); stale views are rejected by the service. */
|
|
192
|
+
async function sendMessage(name, text, expected) {
|
|
193
|
+
if (state.busy.has(name)) return;
|
|
194
|
+
state.busy.add(name);
|
|
113
195
|
render(state.snapshot);
|
|
114
196
|
try {
|
|
115
|
-
await api(`/api/
|
|
116
|
-
state.drafts.delete(
|
|
117
|
-
toast(
|
|
197
|
+
const result = await api(`/api/chats/${encodeURIComponent(name)}/messages`, { method: 'POST', body: { text, expected } });
|
|
198
|
+
state.drafts.delete(`chat:${name}`);
|
|
199
|
+
toast(result.commit ? `Saved (${result.commit.slice(0, 8)}).` : 'Saved.');
|
|
118
200
|
} catch (error) {
|
|
119
|
-
toast(`Not
|
|
201
|
+
toast(`Not sent: ${error.message}`);
|
|
202
|
+
} finally {
|
|
203
|
+
state.busy.delete(name);
|
|
204
|
+
}
|
|
205
|
+
if (state.chat && state.chat.name === name) await loadChat(name);
|
|
206
|
+
else render(state.snapshot);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function startChat(agent, text) {
|
|
210
|
+
if (state.busy.has('new-chat')) return;
|
|
211
|
+
state.busy.add('new-chat');
|
|
212
|
+
render(state.snapshot);
|
|
213
|
+
try {
|
|
214
|
+
// One request key per draft: a retried request returns the chat it already created.
|
|
215
|
+
state.newChatKey = state.newChatKey || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
216
|
+
// Without an explicit agent the service uses Chatter (diagnosed, never silently replaced).
|
|
217
|
+
const result = await api('/api/chats', { method: 'POST', body: { ...(agent ? { agent } : {}), text, requestKey: state.newChatKey } });
|
|
218
|
+
state.newChatKey = null;
|
|
219
|
+
state.drafts.delete('chat:new');
|
|
220
|
+
toast(`Started “${result.name}”.`);
|
|
221
|
+
openChat(result.name);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
toast(`Not started: ${error.message}`);
|
|
120
224
|
} finally {
|
|
121
|
-
state.busy.delete(
|
|
225
|
+
state.busy.delete('new-chat');
|
|
226
|
+
render(state.snapshot);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function showNewChat() {
|
|
231
|
+
state.newChat = 'entry';
|
|
232
|
+
state.focusChatter = true;
|
|
233
|
+
state.chat = null;
|
|
234
|
+
render(state.snapshot);
|
|
235
|
+
if (!state.agents) {
|
|
236
|
+
try {
|
|
237
|
+
state.agents = (await api('/api/chat-agents')).agents;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
toast(`Agents unavailable: ${error.message}`);
|
|
240
|
+
}
|
|
122
241
|
render(state.snapshot);
|
|
123
242
|
}
|
|
124
243
|
}
|
|
125
244
|
|
|
245
|
+
/** Text area with a persistent draft (survives re-renders and reconnects). */
|
|
246
|
+
function composer(key, label, onSend, disabled) {
|
|
247
|
+
const field = h('textarea', { 'data-draft': key, 'aria-label': label, placeholder: 'Type a message…', disabled });
|
|
248
|
+
field.value = state.drafts.get(key) || '';
|
|
249
|
+
field.addEventListener('input', () => state.drafts.set(key, field.value));
|
|
250
|
+
field.addEventListener('keydown', (event) => {
|
|
251
|
+
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
if (field.value.trim()) onSend(field.value);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return h('div', {}, field, h('div', { className: 'row', style: 'margin-top:8px' }, h('button', { className: 'primary', disabled, onclick: () => (field.value.trim() ? onSend(field.value) : field.focus()) }, 'Send'), h('span', { className: 'muted' }, 'Ctrl+Enter sends')));
|
|
257
|
+
}
|
|
258
|
+
|
|
126
259
|
function renderAttention(snapshot) {
|
|
127
|
-
const
|
|
128
|
-
const card = h('section', { className: `card attention ${
|
|
129
|
-
card.append(h('h2', {}, h('span', {},
|
|
130
|
-
if (!
|
|
131
|
-
card.append(h('p', { className: 'muted' }, '
|
|
260
|
+
const waiting = snapshot.chats.waiting;
|
|
261
|
+
const card = h('section', { className: `card attention ${waiting.length ? '' : 'empty'}`, 'aria-label': 'Needs your attention' });
|
|
262
|
+
card.append(h('h2', {}, h('span', {}, waiting.length ? `Needs your attention (${waiting.length})` : 'Needs your attention'), waiting.length ? pill('waiting for you', 'warn') : null));
|
|
263
|
+
if (!waiting.length) {
|
|
264
|
+
card.append(h('p', { className: 'muted' }, 'No agent is waiting for you.'));
|
|
132
265
|
return card;
|
|
133
266
|
}
|
|
134
|
-
for (const
|
|
135
|
-
const busy = state.busy.has(
|
|
136
|
-
const item = h('article', { className: 'request', 'data-
|
|
137
|
-
item.append(h('div', { className: 'reason' },
|
|
138
|
-
|
|
139
|
-
item.append(
|
|
267
|
+
for (const chat of waiting) {
|
|
268
|
+
const busy = state.busy.has(chat.name);
|
|
269
|
+
const item = h('article', { className: 'request', 'data-chat': chat.name });
|
|
270
|
+
item.append(h('div', { className: 'reason' }, `${chat.awaiting ? chat.awaiting.sender : chat.agent || 'An agent'} asks in “${chat.name}”`));
|
|
271
|
+
item.append(h('div', { className: 'meta' }, `${chat.members.join(', ')}${chat.links.length ? ` · ${chat.links.join(', ')}` : ''}${chat.last ? ` · ${time(chat.last.at)}` : ''}${chat.unread ? ` · ${chat.unread} unread` : ''}`));
|
|
272
|
+
if (chat.awaiting) item.append(h('div', { className: 'question' }, chat.awaiting.text));
|
|
273
|
+
const actions = h('div', { className: 'choices', role: 'group', 'aria-label': 'Answers' });
|
|
274
|
+
for (const choice of (chat.awaiting && chat.awaiting.choices) || []) actions.append(h('button', { className: 'primary', disabled: busy, onclick: () => sendMessage(chat.name, choice, chat.messages) }, choice));
|
|
275
|
+
actions.append(h('button', { onclick: () => openChat(chat.name) }, 'Open conversation'));
|
|
276
|
+
item.append(actions);
|
|
277
|
+
card.append(item);
|
|
278
|
+
}
|
|
279
|
+
return card;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function renderThread(snapshot) {
|
|
283
|
+
const chat = state.chat;
|
|
284
|
+
const card = h('section', { className: 'card chats thread', 'aria-label': 'Conversation' });
|
|
285
|
+
if (!chat.detail) {
|
|
286
|
+
card.append(h('h2', {}, h('span', {}, chat.name)), h('p', { className: 'muted' }, 'Loading…'));
|
|
287
|
+
return card;
|
|
288
|
+
}
|
|
289
|
+
const { summary, messages } = chat.detail;
|
|
290
|
+
card.append(
|
|
291
|
+
h(
|
|
292
|
+
'h2',
|
|
293
|
+
{},
|
|
294
|
+
h('span', {}, summary.name),
|
|
295
|
+
h('span', { className: 'row' }, pill(summary.status === 'waiting-for-user' ? 'waiting for you' : summary.status, summary.status === 'waiting-for-user' ? 'warn' : summary.status === 'open' ? 'ok' : ''), h('button', { onclick: () => ((state.chat = null), render(state.snapshot)) }, 'Close')),
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
card.append(h('div', { className: 'meta muted' }, `${summary.members.join(', ')} · ${summary.path}${summary.links.length ? ` · links ${summary.links.join(', ')}` : ''}`));
|
|
299
|
+
const thread = h('div', { className: 'messages', role: 'log', 'aria-live': 'polite' });
|
|
300
|
+
for (const message of messages) {
|
|
301
|
+
const user = message.sender.replace(/^@/, '').toLowerCase() === 'user';
|
|
302
|
+
thread.append(
|
|
140
303
|
h(
|
|
141
|
-
'
|
|
142
|
-
{ className: '
|
|
143
|
-
|
|
304
|
+
'article',
|
|
305
|
+
{ className: `message ${user ? 'user' : 'agent'}` },
|
|
306
|
+
h('div', { className: 'meta' }, h('strong', {}, user ? 'You' : message.sender), ` · ${time(message.at)}`),
|
|
307
|
+
h('div', { className: 'text' }, message.text),
|
|
308
|
+
message.choices && message.choices.length ? h('div', { className: 'muted' }, `Offered: ${message.choices.join(' | ')}`) : null,
|
|
144
309
|
),
|
|
145
310
|
);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
item.append(field);
|
|
160
|
-
item.append(
|
|
161
|
-
h(
|
|
162
|
-
'div',
|
|
163
|
-
{ className: 'row', style: 'margin-top:8px' },
|
|
164
|
-
h('button', { className: 'primary', disabled: busy, onclick: () => (field.value.trim() ? answer(request.id, field.value) : field.focus()) }, 'Send answer'),
|
|
165
|
-
h('span', { className: 'muted' }, 'Ctrl+Enter sends'),
|
|
166
|
-
),
|
|
167
|
-
);
|
|
311
|
+
}
|
|
312
|
+
const last = messages[messages.length - 1];
|
|
313
|
+
if (last && last.sender.replace(/^@/, '').toLowerCase() === 'user' && summary.status === 'open') thread.append(h('p', { className: 'muted' }, `Waiting for ${summary.agent || 'the agent'}’s reply…`));
|
|
314
|
+
card.append(thread);
|
|
315
|
+
requestAnimationFrame(() => {
|
|
316
|
+
thread.scrollTop = thread.scrollHeight;
|
|
317
|
+
});
|
|
318
|
+
const busy = state.busy.has(summary.name);
|
|
319
|
+
if (summary.humanFacing) {
|
|
320
|
+
if (summary.awaiting && summary.awaiting.choices && summary.awaiting.choices.length && summary.status === 'waiting-for-user') {
|
|
321
|
+
card.append(h('div', { className: 'choices', role: 'group', 'aria-label': 'Offered answers' }, summary.awaiting.choices.map((choice) => h('button', { className: 'primary', disabled: busy, onclick: () => sendMessage(summary.name, choice, messages.length) }, choice))));
|
|
168
322
|
}
|
|
169
|
-
|
|
170
|
-
|
|
323
|
+
card.append(composer(`chat:${summary.name}`, `Message to ${summary.agent || 'the agent'}`, (text) => sendMessage(summary.name, text, messages.length), busy));
|
|
324
|
+
} else card.append(h('p', { className: 'muted' }, 'A task-internal consultation between agents (read-only).'));
|
|
325
|
+
return card;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* New chat: first exactly two choices — Chat with Chatter (focused default) or Choose another agent — and only the
|
|
330
|
+
* second lists every valid alternative (project agents, hidden/core agents and Expert).
|
|
331
|
+
*/
|
|
332
|
+
function renderNewChat() {
|
|
333
|
+
const card = h('section', { className: 'card chats', 'aria-label': 'New chat' });
|
|
334
|
+
const back = state.newChat === 'all' || state.newChat === 'chatter' ? h('button', { onclick: () => ((state.newChat = 'entry'), (state.focusChatter = true), render(state.snapshot)) }, 'Back') : null;
|
|
335
|
+
card.append(h('h2', {}, h('span', {}, 'New chat'), h('span', { className: 'row' }, back, h('button', { onclick: () => ((state.newChat = false), render(state.snapshot)) }, 'Cancel'))));
|
|
336
|
+
if (!state.agents) {
|
|
337
|
+
card.append(h('p', { className: 'muted' }, 'Loading agents…'));
|
|
338
|
+
return card;
|
|
339
|
+
}
|
|
340
|
+
const chatter = state.agents.find((agent) => agent.group === 'chatter');
|
|
341
|
+
const others = state.agents.filter((agent) => agent.group !== 'chatter');
|
|
342
|
+
if (!chatter) {
|
|
343
|
+
card.append(h('div', { className: 'notice error' }, 'Chatter (agents/.core/chatter.book), the default partner of new chats, is missing or invalid. Run `ptbk init` or `ptbk start` to restore it, or choose another agent explicitly below.'));
|
|
344
|
+
if (state.newChat === 'entry') state.newChat = 'all';
|
|
345
|
+
}
|
|
346
|
+
if (state.newChat === 'entry' && chatter) {
|
|
347
|
+
const primary = h('button', { className: 'primary', 'data-focus': 'chatter', onclick: () => ((state.newChat = 'chatter'), render(state.snapshot)) }, `Chat with ${chatter.name}`);
|
|
348
|
+
card.append(
|
|
349
|
+
h('div', { className: 'choices', role: 'group', 'aria-label': 'Who do you want to talk to?' }, primary, h('button', { onclick: () => ((state.newChat = 'all'), render(state.snapshot)) }, 'Choose another agent')),
|
|
350
|
+
h('p', { className: 'muted' }, `${chatter.name} answers project questions, consults the right specialists for you and creates tasks only when you ask for work.`),
|
|
351
|
+
);
|
|
352
|
+
if (state.focusChatter) {
|
|
353
|
+
state.focusChatter = false;
|
|
354
|
+
requestAnimationFrame(() => primary.focus());
|
|
355
|
+
}
|
|
356
|
+
return card;
|
|
171
357
|
}
|
|
358
|
+
if (state.newChat === 'chatter' && chatter) {
|
|
359
|
+
card.append(h('p', {}, 'Talking to ', h('strong', {}, chatter.name)));
|
|
360
|
+
card.append(composer('chat:new', `First message to ${chatter.name}`, (text) => startChat(chatter.reference, text), state.busy.has('new-chat')));
|
|
361
|
+
} else {
|
|
362
|
+
const select = h('select', { 'aria-label': 'Agent', 'data-draft': 'chat:new-agent' });
|
|
363
|
+
const groups = [
|
|
364
|
+
['Project agents', others.filter((agent) => agent.group === 'ordinary')],
|
|
365
|
+
['Hidden/core agents', others.filter((agent) => agent.group === 'hidden')],
|
|
366
|
+
['Engine-provided', others.filter((agent) => agent.group === 'expert')],
|
|
367
|
+
];
|
|
368
|
+
for (const [label, members] of groups) if (members.length) select.append(h('optgroup', { label }, members.map((agent) => h('option', { value: agent.reference }, agent.name))));
|
|
369
|
+
if (state.drafts.get('chat:new-agent')) select.value = state.drafts.get('chat:new-agent');
|
|
370
|
+
select.addEventListener('change', () => state.drafts.set('chat:new-agent', select.value));
|
|
371
|
+
card.append(h('div', { className: 'row' }, h('label', {}, 'Talk to ', select)));
|
|
372
|
+
card.append(composer('chat:new', 'First message', (text) => startChat(select.value, text), state.busy.has('new-chat')));
|
|
373
|
+
}
|
|
374
|
+
card.append(h('p', { className: 'muted' }, 'Every message is saved in chats/ and committed. Agents reply read-only and create ordinary tasks only when you ask for work.'));
|
|
375
|
+
return card;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function renderChats(snapshot) {
|
|
379
|
+
if (state.newChat) return renderNewChat();
|
|
380
|
+
if (state.chat) return renderThread(snapshot);
|
|
381
|
+
const chats = snapshot.chats;
|
|
382
|
+
const card = h('section', { className: 'card chats', 'aria-label': 'Chats' });
|
|
383
|
+
card.append(h('h2', {}, h('span', {}, `Chats (${chats.total}${chats.unread ? `, ${chats.unread} unread` : ''})`), h('button', { className: 'primary', onclick: showNewChat }, 'New chat')));
|
|
384
|
+
if (!chats.recent.length) {
|
|
385
|
+
card.append(h('p', { className: 'muted' }, 'No chats yet. Start one to ask an agent about the project or to plan work.'));
|
|
386
|
+
return card;
|
|
387
|
+
}
|
|
388
|
+
const items = state.showAllChats ? chats.recent : chats.recent.slice(0, 6);
|
|
389
|
+
card.append(
|
|
390
|
+
h(
|
|
391
|
+
'ul',
|
|
392
|
+
{ className: 'list' },
|
|
393
|
+
items.map((chat) =>
|
|
394
|
+
h(
|
|
395
|
+
'li',
|
|
396
|
+
{},
|
|
397
|
+
h('a', { href: '#', className: 'title', onclick: (event) => (event.preventDefault(), openChat(chat.name)) }, chat.name, h('span', { className: 'why' }, `${chat.members.join(', ')}${chat.last ? ` · ${chat.last.sender}: ${chat.last.preview}` : ''}`)),
|
|
398
|
+
chat.status === 'waiting-for-user' ? pill('waiting for you', 'warn') : chat.unread ? pill(`${chat.unread} unread`, 'ok') : pill(chat.status, ''),
|
|
399
|
+
),
|
|
400
|
+
),
|
|
401
|
+
),
|
|
402
|
+
);
|
|
403
|
+
if (chats.recent.length > 6) card.append(h('button', { onclick: () => ((state.showAllChats = !state.showAllChats), render(state.snapshot)) }, state.showAllChats ? 'Show fewer' : `Show all ${chats.recent.length}`));
|
|
172
404
|
return card;
|
|
173
405
|
}
|
|
174
406
|
|
|
@@ -177,7 +409,6 @@
|
|
|
177
409
|
card.append(h('h2', {}, h('span', {}, 'Current work'), pill(snapshot.phase, snapshot.phase === 'blocked' ? 'bad' : snapshot.current ? 'ok' : '')));
|
|
178
410
|
const current = snapshot.current;
|
|
179
411
|
if (current) {
|
|
180
|
-
const model = current.model ? `${current.model}${current.modelSource === 'requested' ? ' (requested)' : ''}` : 'harness default (unknown model)';
|
|
181
412
|
card.append(
|
|
182
413
|
h(
|
|
183
414
|
'div',
|
|
@@ -187,9 +418,8 @@
|
|
|
187
418
|
),
|
|
188
419
|
);
|
|
189
420
|
const rows = [
|
|
190
|
-
['Task', `${current.
|
|
421
|
+
['Task', `${current.taskRef}${current.kind !== 'task' ? ` (${current.kind})` : ''}`],
|
|
191
422
|
...(current.trace ? [['Trace', current.trace]] : []),
|
|
192
|
-
['Harness', `${current.harnessName} · ${model}${current.thinkingLevel ? ` · ${current.thinkingLevel}` : ''}`],
|
|
193
423
|
['Phase', `${current.phase}${current.attempt ? ` · attempt ${current.attempt}/${current.attemptBudget}` : ''}${current.detail ? ` · ${current.detail}` : ''}`],
|
|
194
424
|
['Since', time(current.startedAt)],
|
|
195
425
|
];
|
|
@@ -216,27 +446,112 @@
|
|
|
216
446
|
}
|
|
217
447
|
const error = snapshot.errors[snapshot.errors.length - 1];
|
|
218
448
|
if (error) card.append(h('details', {}, h('summary', {}, `Last error: ${error.summary}`), h('div', { className: 'muted' }, [error.phase && `Step: ${error.phase}`, error.next && `Next: ${error.next}`, error.trace && `Diagnostics: ${error.trace}`].filter(Boolean).join(' · '))));
|
|
449
|
+
// A significant browser failure stays visible while the browser details are collapsed.
|
|
450
|
+
const browser = snapshot.browser;
|
|
451
|
+
if (browser && (browser.state === 'failed' || (browser.recovery && browser.recovery.state === 'blocked')) && browser.lastFailure) {
|
|
452
|
+
card.append(h('div', { className: 'notice error' }, h('strong', {}, 'Project browser'), ` — ${browser.lastFailure.summary}`, browser.recovery && browser.recovery.state === 'retrying' ? h('div', { className: 'muted' }, 'Recovering automatically…') : null, h('div', { className: 'muted' }, 'Open “Browser details” to recheck, retry or restart it.')));
|
|
453
|
+
}
|
|
219
454
|
return card;
|
|
220
455
|
}
|
|
221
456
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
457
|
+
/** Labels of a model in one execution context (reported/requested/automatic/unknown stay distinct). */
|
|
458
|
+
function modelText(model) {
|
|
459
|
+
if (model.status === 'reported') return `${model.value} (reported by the harness)`;
|
|
460
|
+
if (model.status === 'requested') return `${model.value} (requested; not yet confirmed)`;
|
|
461
|
+
if (model.status === 'automatic') return 'automatic — harness default, concrete model not reported';
|
|
462
|
+
return 'unknown — not reported';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Labels of a thinking level, in the harness's own terminology. */
|
|
466
|
+
function thinkingText(thinking) {
|
|
467
|
+
const term = thinking.term ? ` — ${thinking.term}` : '';
|
|
468
|
+
if (thinking.status === 'requested') return `${thinking.value}${term}`;
|
|
469
|
+
if (thinking.status === 'automatic') return `automatic (harness default, level not reported)${term}`;
|
|
470
|
+
if (thinking.status === 'not-applicable') return 'not applicable — this harness has no thinking-level setting';
|
|
471
|
+
return thinking.value ? `${thinking.value} (unverified)` : 'unknown — not reported';
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** One collapsible detail panel whose open state is a viewer-local choice. */
|
|
475
|
+
function detailPanel(key, title, content) {
|
|
476
|
+
const element = h('details', { className: 'card', open: state.details[key] }, h('summary', {}, title), content);
|
|
477
|
+
element.addEventListener('toggle', () => {
|
|
478
|
+
state.details[key] = element.open;
|
|
479
|
+
});
|
|
480
|
+
return element;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function renderExecutionDetails(snapshot) {
|
|
484
|
+
const execution = snapshot.execution;
|
|
485
|
+
if (!execution) return h('p', { className: 'muted' }, 'No execution yet in this service.');
|
|
486
|
+
const rows = [
|
|
487
|
+
[execution.status === 'active' ? 'Current run' : 'Last run (historical)', execution.status === 'active' ? `${execution.task.kind} · phase ${execution.phase || 'unknown'}${execution.attempt ? ` · attempt ${execution.attempt}/${execution.attemptBudget}` : ''}` : `${execution.outcome || ''} ${execution.endedAt ? `at ${time(execution.endedAt)}` : ''} — not running now`],
|
|
488
|
+
['Task', `${execution.task.title}${execution.task.source ? ` · ${execution.task.source}` : ''}`],
|
|
489
|
+
['Agent', `${execution.agent.name}${execution.agent.source ? ` · ${execution.agent.source}` : ''}`],
|
|
490
|
+
];
|
|
491
|
+
for (const context of execution.contexts) {
|
|
492
|
+
const prefix = context.role === 'consultation' ? `${context.by || '?'} → ${context.agent} (${context.state}): ` : '';
|
|
493
|
+
rows.push([context.role === 'consultation' ? 'Consultation' : 'Harness', `${prefix}${context.harness.name}${context.harness.version ? ` ${context.harness.version}` : ''}`]);
|
|
494
|
+
rows.push(['Model', `${prefix}${modelText(context.model)}${context.model.source ? ` · ${context.model.source}` : ''}`]);
|
|
495
|
+
rows.push(['Thinking', `${prefix}${thinkingText(context.thinkingLevel)}${context.thinkingLevel.source ? ` · ${context.thinkingLevel.source}` : ''}`]);
|
|
496
|
+
}
|
|
497
|
+
return h('dl', { className: 'kv' }, rows.map(([key, value]) => [h('dt', {}, key), h('dd', {}, value)]));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function renderBrowserDetails(snapshot) {
|
|
501
|
+
const browser = snapshot.browser;
|
|
502
|
+
if (!browser) return h('p', { className: 'muted' }, 'Browser information is not available from this service.');
|
|
503
|
+
const rows = [
|
|
504
|
+
['Profile', `${browser.sourceLabel} (${browser.source})`],
|
|
505
|
+
['Path', browser.profilePath],
|
|
506
|
+
['Mode', `${browser.mode.requested}${browser.mode.actual && browser.mode.actual !== browser.mode.requested ? ` (actually ${browser.mode.actual})` : ''}${browser.mode.note ? ` · ${browser.mode.note}` : ''}`],
|
|
507
|
+
['State', `${browser.state}${browser.pid ? ` · PID ${browser.pid}` : ''}`],
|
|
508
|
+
['Ownership', `${browser.ownership.state}${browser.ownership.holder ? ` — ${browser.ownership.holder}` : ''}`],
|
|
509
|
+
];
|
|
510
|
+
if (browser.executable) rows.push(['Browser', browser.executable]);
|
|
511
|
+
if (browser.savedProfile) rows.push(['Saved', browser.savedProfile]);
|
|
512
|
+
if (browser.pending) rows.push(['Pending', browser.pending]);
|
|
513
|
+
if (browser.lastFailure) rows.push(['Last failure', `${time(browser.lastFailure.at)} ${browser.lastFailure.summary}${browser.lastFailure.evidence ? ` — ${browser.lastFailure.evidence}` : ''}`]);
|
|
514
|
+
if (browser.recovery && (browser.recovery.state !== 'idle' || browser.recovery.attempts)) rows.push(['Recovery', `${browser.recovery.state}${browser.recovery.attempts ? ` · ${browser.recovery.attempts} attempt(s)` : ''}${browser.recovery.nextRetryAt ? ` · next ${time(browser.recovery.nextRetryAt)}` : ''}${browser.recovery.blocker ? ` · ${browser.recovery.blocker}` : ''}`]);
|
|
515
|
+
if (browser.lastAction) rows.push(['Last action', `${browser.lastAction.action}: ${browser.lastAction.state} — ${browser.lastAction.outcome}`]);
|
|
516
|
+
return h(
|
|
517
|
+
'div',
|
|
518
|
+
{},
|
|
519
|
+
h('dl', { className: 'kv' }, rows.map(([key, value]) => [h('dt', {}, key), h('dd', {}, value)])),
|
|
228
520
|
h(
|
|
229
521
|
'div',
|
|
230
|
-
{ className: 'row' },
|
|
231
|
-
h('button', {
|
|
232
|
-
h('button', {
|
|
233
|
-
|
|
234
|
-
? h('button', { onclick: () => control('cancel-stop') }, 'Cancel stop')
|
|
235
|
-
: h('button', { disabled: !running, onclick: () => control('stop', 'Stop the service after the current task? Closing this page does not stop it.') }, 'Stop after task'),
|
|
236
|
-
h('button', { className: 'danger', disabled: !running, onclick: () => control('stop-now', 'Stop now? The current task is cancelled and resumes from its recovery record next time.') }, 'Stop now'),
|
|
522
|
+
{ className: 'row', style: 'margin-top:8px', role: 'group', 'aria-label': 'Browser recovery' },
|
|
523
|
+
h('button', { onclick: () => control('browser-recheck') }, 'Recheck'),
|
|
524
|
+
h('button', { onclick: () => control('browser-retry') }, 'Retry startup'),
|
|
525
|
+
h('button', { onclick: () => control('browser-restart', 'Restart this project’s browser? Its profile and sessions are kept; the engine keeps running.') }, 'Restart browser'),
|
|
237
526
|
),
|
|
527
|
+
h('p', { className: 'muted' }, 'Recovery is automatic; these actions use the same engine-owned recovery and never restart the engine or other browsers.'),
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function renderDetails(snapshot) {
|
|
532
|
+
return h('section', { className: 'details-panels', 'aria-label': 'Details' }, detailPanel('execution', 'Execution details — agent, harness, model, thinking level', renderExecutionDetails(snapshot)), detailPanel('browser', 'Browser details — profile, health, recovery', renderBrowserDetails(snapshot)));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** The always-visible service-control panel: live state, pending intent and the shared lifecycle operations. */
|
|
536
|
+
function renderControls(snapshot) {
|
|
537
|
+
const stateName = snapshot.service.state === 'paused' ? 'pause' : snapshot.service.state;
|
|
538
|
+
const card = h('section', { className: `card controls ${stateName}`, 'aria-label': 'Service controls' });
|
|
539
|
+
const lifecycle = snapshot.service.lifecycle || {};
|
|
540
|
+
card.append(h('h2', {}, h('span', {}, 'Service controls'), h('span', { className: 'state' }, pill(stateName, stateName === 'running' ? 'ok' : stateName === 'stopping' ? 'bad' : 'warn'))));
|
|
541
|
+
card.append(h('p', {}, lifecycleText(snapshot), lifecycle.since ? h('span', { className: 'muted' }, ` Requested ${time(lifecycle.since)}.`) : null));
|
|
542
|
+
const buttons = lifecycleActions(snapshot).map((item) =>
|
|
543
|
+
h('button', { className: item.danger ? 'danger' : item.primary ? 'primary' : '', disabled: state.busy.has(`control:${item.action}`), onclick: () => control(item.action, item.confirm, item.extra) }, item.label),
|
|
238
544
|
);
|
|
239
|
-
|
|
545
|
+
buttons.push(h('button', { disabled: !(snapshot.wait && snapshot.wait.skippable), title: snapshot.wait && !snapshot.wait.skippable ? 'Only pacing/backoff waits can be skipped' : '', onclick: () => control('skip-wait') }, 'Skip wait'));
|
|
546
|
+
card.append(h('div', { className: 'row', role: 'group', 'aria-label': 'Lifecycle actions' }, buttons));
|
|
547
|
+
card.append(h('p', { className: 'muted' }, 'These are the same operations as ptbk pause, resume, stop, stop --force and restart; every connected viewer shares this engine. Leaving this page never stops it.'));
|
|
548
|
+
return card;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Agents, harnesses and settings (secondary inventory). */
|
|
552
|
+
function renderInventory(snapshot) {
|
|
553
|
+
const card = h('section', { className: 'card inventory', 'aria-label': 'Agents and settings' });
|
|
554
|
+
card.append(h('h2', {}, 'Agents and settings'));
|
|
240
555
|
const agents = h('details', {}, h('summary', {}, `Agents (${snapshot.agents.length})`), h('ul', { className: 'list' }, snapshot.agents.map((agent) => h('li', {}, h('span', { className: 'row' }, badge(agent, true), agent.name), h('span', { className: 'muted' }, ACTIVITY_LABEL[agent.activity])))));
|
|
241
556
|
card.append(agents);
|
|
242
557
|
const featured = snapshot.harnesses.filter((harness) => harness.featured || harness.enabled);
|
|
@@ -310,7 +625,7 @@
|
|
|
310
625
|
'ul',
|
|
311
626
|
{ className: 'list' },
|
|
312
627
|
snapshot.results.recent.slice(0, 6).map((result) =>
|
|
313
|
-
h('li', {}, h('span', { className: 'title' }, result.title, h('span', { className: 'why' }, `${result.agent || ''} ${result.commit ? `· ${result.commit.slice(0, 8)}` : ''}
|
|
628
|
+
h('li', {}, h('span', { className: 'title' }, result.title, h('span', { className: 'why' }, `${result.agent || ''} ${result.commit ? `· ${result.commit.slice(0, 8)}` : ''} · ${time(result.endedAt)}`)), pill(result.outcome, result.outcome.startsWith('completed') ? 'ok' : result.outcome === 'failed' ? 'bad' : 'warn')),
|
|
314
629
|
),
|
|
315
630
|
),
|
|
316
631
|
);
|
|
@@ -358,7 +673,7 @@
|
|
|
358
673
|
const focusDraft = active && active.getAttribute && active.getAttribute('data-draft');
|
|
359
674
|
const selection = focusDraft ? [active.selectionStart, active.selectionEnd] : null;
|
|
360
675
|
const scroll = window.scrollY;
|
|
361
|
-
app.replaceChildren(renderAttention(snapshot), renderWork(snapshot),
|
|
676
|
+
app.replaceChildren(renderAttention(snapshot), renderControls(snapshot), renderChats(snapshot), renderWork(snapshot), renderDetails(snapshot), renderQueue(snapshot), renderActivity(snapshot), renderInventory(snapshot), renderConnection(snapshot));
|
|
362
677
|
if (focusDraft) {
|
|
363
678
|
const field = app.querySelector(`[data-draft="${CSS.escape(focusDraft)}"]`);
|
|
364
679
|
if (field) {
|
|
@@ -370,9 +685,9 @@
|
|
|
370
685
|
const live = state.connected ? 'live' : 'disconnected — showing last known state';
|
|
371
686
|
headerStatus.replaceChildren(
|
|
372
687
|
h('strong', {}, snapshot.project.name),
|
|
373
|
-
` · ptbk ${snapshot.engine.version} · ${snapshot.service.mode} · ${snapshot.service.state}${snapshot.service.commitMode === 'no-commit' ? ' · NO-COMMIT' : ''}${snapshot.
|
|
688
|
+
` · ptbk ${snapshot.engine.version} · ${snapshot.service.mode} · ${snapshot.service.state}${snapshot.service.commitMode === 'no-commit' ? ' · NO-COMMIT' : ''}${snapshot.chats.waiting.length ? ` · ${snapshot.chats.waiting.length} waiting for you` : ''} · ${live} · observed ${time(snapshot.observedAt)}`,
|
|
374
689
|
);
|
|
375
|
-
document.title = `${snapshot.
|
|
690
|
+
document.title = `${snapshot.chats.waiting.length ? `(${snapshot.chats.waiting.length}) ` : ''}${snapshot.project.name} · Promptbook`;
|
|
376
691
|
}
|
|
377
692
|
|
|
378
693
|
/** Pairing screen for unauthenticated visitors (no project data). */
|
|
@@ -401,7 +716,13 @@
|
|
|
401
716
|
state.snapshot = snapshot;
|
|
402
717
|
state.revision = snapshot.revision;
|
|
403
718
|
render(snapshot);
|
|
719
|
+
// New messages in the open chat arrive without stealing focus or drafts.
|
|
720
|
+
if (state.chat && state.chat.detail) {
|
|
721
|
+
const summary = snapshot.chats.recent.find((chat) => chat.name === state.chat.name);
|
|
722
|
+
if (summary && (summary.messages !== state.chat.detail.messages.length || summary.status !== state.chat.detail.summary.status)) loadChat(state.chat.name);
|
|
723
|
+
}
|
|
404
724
|
if (snapshot.service.state === 'stopped' || snapshot.service.state === 'finished') {
|
|
725
|
+
// An exited engine is an observation; the page never starts or recreates a service.
|
|
405
726
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
406
727
|
}
|
|
407
728
|
} catch (error) {
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { openSync } from 'node:fs';
|
|
3
3
|
import { engineModule } from '../common/version.js';
|
|
4
4
|
import { projectAt } from '../project/workspace.js';
|
|
5
|
-
import { readLaunchMetadata, rotateServiceLog, servicePaths } from './records.js';
|
|
5
|
+
import { readLaunchMetadata, RESTART_EXIT_CODE, rotateServiceLog, servicePaths } from './records.js';
|
|
6
6
|
/**
|
|
7
7
|
* Daemon supervisor entry: `node dist/service/supervisor.js <projectRoot>`.
|
|
8
8
|
*
|
|
@@ -49,6 +49,12 @@ async function supervise() {
|
|
|
49
49
|
worker = undefined;
|
|
50
50
|
if (terminating || code === 0)
|
|
51
51
|
return;
|
|
52
|
+
if (code === RESTART_EXIT_CODE) {
|
|
53
|
+
// A requested restart: the old worker released ownership; start exactly one replacement now.
|
|
54
|
+
process.stdout.write(`${new Date().toISOString()} worker restarted on request\n`);
|
|
55
|
+
backoff = 1000;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
52
58
|
const launch = await readLaunchMetadata(project);
|
|
53
59
|
if (!launch?.persistent || launch.intentionallyStopped)
|
|
54
60
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../src/service/supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../src/service/supervisor.ts"],"names":[],"mappings":"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"}
|