toolroll 0.9.5 → 0.9.6
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/CHANGELOG.md +11 -0
- package/dist/assignment-presentation.d.ts +13 -2
- package/dist/assignment-presentation.js +22 -9
- package/dist/assignment-ui.d.ts +10 -15
- package/dist/assignment-ui.js +17 -18
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +21 -20
- package/dist/browser-workspace.d.ts +3 -10
- package/dist/chat-acceptance.js +2 -2
- package/dist/chat-quiet.js +20 -19
- package/dist/demo-chat.d.ts +0 -5
- package/dist/demo-chat.js +10 -2
- package/dist/lead-status.d.ts +7 -0
- package/dist/lead-status.js +11 -2
- package/dist/serve.js +64 -66
- package/dist/task-status.d.ts +179 -0
- package/dist/task-status.js +317 -0
- package/dist/telegram-progress.js +62 -100
- package/dist/telegram-status.js +19 -6
- package/dist/work-index.js +25 -9
- package/dist/workspace-ui.d.ts +7 -0
- package/dist/workspace-ui.js +30 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.9.6 — 2026-10-01
|
|
6
|
+
|
|
7
|
+
- **Every task's status, clear at a glance.** One headline from a fixed set
|
|
8
|
+
(Queued, Planning, Needs you, Building, Ready for review, Complete, Failed,
|
|
9
|
+
Stopped) and one plain sentence, the same words on the Tasks list, the task
|
|
10
|
+
page, the result, Crew, the Telegram/Slack/Discord/Teams card and
|
|
11
|
+
`toolroll status`. Checks, the pull request, requirements and saved
|
|
12
|
+
evidence sit underneath as quiet rows. A pull request that couldn't open
|
|
13
|
+
is an amber note with one action, never a red chip beside Complete: red
|
|
14
|
+
now means only Failed. The exact technical reason stays under Details.
|
|
15
|
+
|
|
5
16
|
## 0.9.5 — 2026-10-01
|
|
6
17
|
|
|
7
18
|
- **A demo you can feel in 90 seconds.** In `npx toolroll demo`, Chat has a
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* checks, approvals, ownership, or the exact completion identity. */
|
|
4
4
|
import type { AssignmentSnapshot } from './assignment.js';
|
|
5
5
|
import { type DisplayStatus, type WorkStatus } from './workspace-ui.js';
|
|
6
|
+
import { type PullRequestFact, type StatusAction, type TaskStatus, type TaskStatusFacts } from './task-status.js';
|
|
6
7
|
export type AssignmentWorkStatus = DisplayStatus & Partial<Pick<WorkStatus, 'views' | 'rank'>>;
|
|
7
8
|
export type AssignmentAttention = {
|
|
8
9
|
id: string;
|
|
@@ -14,12 +15,22 @@ export type AssignmentAttention = {
|
|
|
14
15
|
export declare function historicalAssessmentReason(reason: string): boolean;
|
|
15
16
|
/** Known storage-cap notices do not mean the retained bytes are damaged. */
|
|
16
17
|
export declare function shortenedMaterialReason(detail: string): boolean;
|
|
17
|
-
export
|
|
18
|
+
export type AssignmentStatusOptions = {
|
|
18
19
|
workStatus?: AssignmentWorkStatus;
|
|
19
20
|
diagnostics?: WorkStatus['diagnostics'];
|
|
20
21
|
additionalAttention?: readonly string[];
|
|
21
|
-
|
|
22
|
+
/** A live planner rather than a builder holds the work. */
|
|
23
|
+
planning?: boolean;
|
|
24
|
+
pullRequest?: PullRequestFact | null;
|
|
25
|
+
action?: StatusAction | null;
|
|
26
|
+
links?: TaskStatusFacts['links'];
|
|
27
|
+
evidence?: TaskStatusFacts['evidence'];
|
|
28
|
+
};
|
|
29
|
+
/** The one shared status (task-status.ts) for this assignment. */
|
|
30
|
+
export declare function assignmentTaskStatusOf(assignment: AssignmentSnapshot, options?: AssignmentStatusOptions): TaskStatus;
|
|
31
|
+
export declare function assignmentPresentationOf(assignment: AssignmentSnapshot, options?: AssignmentStatusOptions): {
|
|
22
32
|
status: WorkStatus;
|
|
33
|
+
taskStatus: TaskStatus;
|
|
23
34
|
primaryAction: import("./work-summary.js").WorkAction | null;
|
|
24
35
|
attention: AssignmentAttention[];
|
|
25
36
|
diagnostics: Pick<DisplayStatus, "detail" | "label" | "token" | "tone">[];
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { GOAL_ASSESSMENT_PENDING, manualReviewCriterionOf } from './proof.js';
|
|
2
2
|
import { failedCheckExit } from './workspace-ui.js';
|
|
3
|
-
|
|
4
|
-
working: 'Working', checking: 'Checking', 'needs-decision': 'Needs your decision',
|
|
5
|
-
'ready-to-check': 'Ready', complete: 'Complete', cancelled: 'Cancelled',
|
|
6
|
-
};
|
|
3
|
+
import { assignmentStatusFacts, taskStatusOf, workToneOf } from './task-status.js';
|
|
7
4
|
/** Only the retired assessment requirement is history-only. Specific missing
|
|
8
5
|
* material, defects and failed checks remain visible, including reviewer findings. */
|
|
9
6
|
export function historicalAssessmentReason(reason) {
|
|
@@ -19,13 +16,29 @@ export function shortenedMaterialReason(detail) {
|
|
|
19
16
|
detail === 'The changed-file list was cut short; the counts are complete.' ||
|
|
20
17
|
/^Saved (?:terminal-diff|report) #\d+ is incomplete\.$/.test(detail);
|
|
21
18
|
}
|
|
19
|
+
/** The one shared status (task-status.ts) for this assignment. */
|
|
20
|
+
export function assignmentTaskStatusOf(assignment, options = {}) {
|
|
21
|
+
const work = options.workStatus;
|
|
22
|
+
return taskStatusOf(assignmentStatusFacts(assignment, {
|
|
23
|
+
work: work === undefined ? null : { token: work.token, ...(work.views === undefined ? {} : { views: work.views }), detail: work.detail },
|
|
24
|
+
...(options.planning === undefined ? {} : { planning: options.planning }),
|
|
25
|
+
...(options.pullRequest === undefined ? {} : { pullRequest: options.pullRequest }),
|
|
26
|
+
...(options.action === undefined ? {} : { action: options.action }),
|
|
27
|
+
...(options.links === undefined ? {} : { links: options.links }),
|
|
28
|
+
...(options.evidence === undefined ? {} : { evidence: options.evidence }),
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
22
31
|
export function assignmentPresentationOf(assignment, options = {}) {
|
|
23
32
|
const { state } = assignment, work = options.workStatus;
|
|
33
|
+
const taskStatus = assignmentTaskStatusOf(assignment, options);
|
|
34
|
+
// The headline and its sentence are the shared words; the token, views and
|
|
35
|
+
// rank stay the existing projection's (pages and tests key off them).
|
|
36
|
+
const shared = { label: taskStatus.headline, tone: workToneOf(taskStatus.headline), detail: taskStatus.sentence, action: null };
|
|
37
|
+
const needsYou = taskStatus.headline === 'Needs you';
|
|
24
38
|
const status = state === 'working'
|
|
25
|
-
? { ...
|
|
26
|
-
|
|
27
|
-
: { token: `assignment-${state}`,
|
|
28
|
-
action: null, tone: state === 'needs-decision' ? 'attention' : state === 'checking' ? work?.tone ?? 'live' : state === 'ready-to-check' ? 'ready' : state === 'complete' ? 'done' : 'muted',
|
|
39
|
+
? { ...shared, token: `assignment-${state}`,
|
|
40
|
+
views: [...new Set([...(work?.views?.filter(view => view !== 'completed') ?? ['all']), ...(needsYou ? ['needs-you'] : [])])], rank: needsYou ? 0 : work?.rank ?? 2 }
|
|
41
|
+
: { ...shared, token: `assignment-${state}`,
|
|
29
42
|
views: state === 'ready-to-check' || state === 'needs-decision' ? ['all', 'needs-you'] : state === 'complete' ? ['all', 'completed']
|
|
30
43
|
: state === 'checking' && work?.views?.includes('running') ? ['all', 'running'] : ['all'],
|
|
31
44
|
rank: state === 'needs-decision' || state === 'ready-to-check' ? 0 : state === 'checking' ? 1 : state === 'complete' ? 3 : 4 };
|
|
@@ -78,5 +91,5 @@ export function assignmentPresentationOf(assignment, options = {}) {
|
|
|
78
91
|
return false;
|
|
79
92
|
return true;
|
|
80
93
|
});
|
|
81
|
-
return { status, primaryAction: assignment.primaryAction, attention: [...attention.values()], diagnostics };
|
|
94
|
+
return { status, taskStatus, primaryAction: assignment.primaryAction, attention: [...attention.values()], diagnostics };
|
|
82
95
|
}
|
package/dist/assignment-ui.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* existing projections and signed operating-mode terms, never this renderer. */
|
|
3
3
|
import type { AssignmentSnapshot } from './assignment.js';
|
|
4
4
|
import type { DisplayStatus, StatusTone, WorkStatus } from './workspace-ui.js';
|
|
5
|
-
import { type AssignmentWorkStatus } from './assignment-presentation.js';
|
|
5
|
+
import { type AssignmentStatusOptions, type AssignmentWorkStatus } from './assignment-presentation.js';
|
|
6
|
+
import { type PullRequestFact, type TaskStatus } from './task-status.js';
|
|
6
7
|
/** The existing verified receipt reader may discover damage after a verdict
|
|
7
8
|
* was saved. Keep the limitation visible without inventing another work stage. */
|
|
8
9
|
export declare function assignmentWithEvidence(assignment: AssignmentSnapshot, resultStatus: DisplayStatus | null, resultRunId?: number | null): AssignmentSnapshot;
|
|
@@ -19,38 +20,32 @@ export type AssignmentAttemptRow = {
|
|
|
19
20
|
/** Every version of the assignment, oldest first, as the card lists them. */
|
|
20
21
|
export declare function assignmentAttemptsOf(assignment: AssignmentSnapshot): AssignmentAttemptRow[];
|
|
21
22
|
export declare function assignmentAttemptsHtml(assignment: AssignmentSnapshot): string;
|
|
22
|
-
type AssignmentCardOptions = {
|
|
23
|
+
type AssignmentCardOptions = Omit<AssignmentStatusOptions, 'links'> & {
|
|
23
24
|
hideAction?: boolean;
|
|
24
25
|
problem?: boolean;
|
|
25
|
-
diagnostics?: WorkStatus['diagnostics'];
|
|
26
|
-
workStatus?: AssignmentWorkStatus;
|
|
27
26
|
resultHref?: string;
|
|
27
|
+
pullRequest?: PullRequestFact | null;
|
|
28
28
|
};
|
|
29
29
|
/** The status card's content: one projection for the server HTML and the
|
|
30
|
-
* rebuilt task page, so the two never word the same state differently.
|
|
30
|
+
* rebuilt task page, so the two never word the same state differently. The
|
|
31
|
+
* headline, sentence and detail rows are the shared task status; the exact
|
|
32
|
+
* recorded reasons stay one tap away under Details, never as red text. */
|
|
31
33
|
export type AssignmentCard = {
|
|
32
34
|
token: string;
|
|
33
35
|
tone: StatusTone;
|
|
34
36
|
label: string;
|
|
37
|
+
status: TaskStatus;
|
|
35
38
|
action: {
|
|
36
39
|
label: string;
|
|
37
40
|
href: string;
|
|
38
41
|
openResult: boolean;
|
|
39
42
|
} | null;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
by: string | null;
|
|
43
|
-
} | null;
|
|
44
|
-
detail: {
|
|
45
|
-
text: string;
|
|
46
|
-
problem: boolean;
|
|
47
|
-
};
|
|
48
|
-
problems: string[];
|
|
43
|
+
/** Exact technical reasons and diagnostics, shown on request. */
|
|
44
|
+
reasons: string[];
|
|
49
45
|
diagnostics: {
|
|
50
46
|
token: string;
|
|
51
47
|
label: string;
|
|
52
48
|
detail: string;
|
|
53
|
-
problem: boolean;
|
|
54
49
|
}[];
|
|
55
50
|
notices: {
|
|
56
51
|
summary: string;
|
package/dist/assignment-ui.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { assignmentPresentationOf, shortenedMaterialReason } from './assignment-presentation.js';
|
|
2
2
|
import { plainReasonWords } from './proof.js';
|
|
3
|
+
import { chatResultHref } from './chat-controls.js';
|
|
4
|
+
import { statusDetailsHtml, statusWhyHtml } from './task-status.js';
|
|
3
5
|
const escape = (value) => value.replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char]);
|
|
4
6
|
/** The existing verified receipt reader may discover damage after a verdict
|
|
5
7
|
* was saved. Keep the limitation visible without inventing another work stage. */
|
|
@@ -63,25 +65,23 @@ function savedMaterialNotice(detail, assignment) {
|
|
|
63
65
|
!assignment.receipt.artifacts.some(one => one.id === Number(earlier[1])) ? 'history' : null;
|
|
64
66
|
}
|
|
65
67
|
export function assignmentCardOf(assignment, options = {}) {
|
|
66
|
-
const
|
|
67
|
-
const {
|
|
68
|
+
const receipt = assignment.receipt;
|
|
69
|
+
const links = receipt === null ? {} : { result: options.resultHref ?? chatResultHref(receipt.taskId, receipt.runId), checks: chatResultHref(receipt.taskId, receipt.runId, 'checks'),
|
|
70
|
+
pullRequest: `/t/${encodeURIComponent(assignment.rootId)}#merge` };
|
|
71
|
+
const presentation = assignmentPresentationOf(assignment, { ...options, links });
|
|
72
|
+
const { status, diagnostics, taskStatus } = presentation;
|
|
68
73
|
const href = assignment.primaryAction?.code === 'open-result' && options.resultHref !== undefined ? options.resultHref : assignmentActionHref(assignment);
|
|
69
74
|
const attention = presentation.attention.map(one => one.detail);
|
|
70
75
|
const notices = [...attention, ...diagnostics.map(one => one.detail)].filter(one => savedMaterialNotice(one, assignment) !== null);
|
|
71
76
|
const partial = notices.some(one => savedMaterialNotice(one, assignment) === 'partial');
|
|
72
77
|
const history = notices.some(one => savedMaterialNotice(one, assignment) === 'history');
|
|
73
|
-
const
|
|
74
|
-
const checkProblem = ready && assignment.receipt !== null && assignment.receipt.completionKind !== 'research-report' && assignment.receipt.checks.status !== 'passed';
|
|
78
|
+
const recorded = assignment.detail === taskStatus.sentence || taskStatus.headline === 'Ready for review' || taskStatus.headline === 'Complete' ? [] : [assignment.detail];
|
|
75
79
|
return {
|
|
76
|
-
token: status.token, tone: status.tone, label: status.label,
|
|
80
|
+
token: status.token, tone: status.tone, label: status.label, status: taskStatus,
|
|
77
81
|
action: options.hideAction || href === null ? null : { label: assignment.primaryAction.label, href, openResult: options.resultHref !== undefined },
|
|
78
|
-
|
|
79
|
-
passed: ready && !checkProblem && assignment.receipt !== null && assignment.receipt.checks.status === 'passed'
|
|
80
|
-
? { by: assignment.completion === null ? null : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, '') } : null,
|
|
81
|
-
detail: { text: assignment.detail, problem: checkProblem || options.problem === true && !ready },
|
|
82
|
-
problems: attention.filter(one => savedMaterialNotice(one, assignment) === null).map(plainReasonWords),
|
|
82
|
+
reasons: [...new Set([...recorded, ...attention.filter(one => savedMaterialNotice(one, assignment) === null).map(plainReasonWords)])].filter(one => one !== taskStatus.sentence),
|
|
83
83
|
diagnostics: diagnostics.filter(one => savedMaterialNotice(one.detail, assignment) === null)
|
|
84
|
-
.map(one => ({ token: one.token, label: one.label, detail: one.detail
|
|
84
|
+
.map(one => ({ token: one.token, label: one.label, detail: one.detail })),
|
|
85
85
|
notices: notices.length === 0 ? null : { summary: [partial ? 'Saved output is partial' : '', history ? 'Earlier material unavailable' : ''].filter(Boolean).join(' · '), lines: [...new Set(notices)] },
|
|
86
86
|
attempts: assignmentAttemptsOf(assignment),
|
|
87
87
|
lead: assignment.owner === null ? null : { label: assignment.owner.label, active: assignment.owner.active },
|
|
@@ -89,14 +89,13 @@ export function assignmentCardOf(assignment, options = {}) {
|
|
|
89
89
|
}
|
|
90
90
|
export function assignmentSummaryHtml(assignment, options = {}) {
|
|
91
91
|
const card = assignmentCardOf(assignment, options);
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const tone = card.status.tone;
|
|
93
|
+
return `<section class="${options.compact ? 'assignment-summary' : 'card assignment-summary'}" aria-label="assignment progress" data-assignment="${escape(assignment.rootId)}" data-work-status="${card.token}" data-tone="${card.tone}" data-headline="${escape(card.status.headline)}" data-headline-tone="${tone}"${options.compact ? '' : ' data-task-status'}>` +
|
|
94
|
+
(options.compact ? `<span class="status-line" data-work-status="${card.token}" data-tone="${card.tone}"><i class="status-dot" aria-hidden="true"></i><span class="status-label">${card.label}</span></span>` : `<h2 class="assignment-state status-headline"><i aria-hidden="true"></i>${escape(card.label)}</h2>`) +
|
|
95
|
+
`<p class="meta assignment-detail status-sentence">${escape(card.status.sentence)}</p>` +
|
|
94
96
|
(card.action === null ? '' : `<a class="${options.compact ? 'work-action' : 'button-link'}" href="${escape(card.action.href)}"${card.action.openResult ? ' data-open-result' : ''}${options.compact ? '' : ' data-primary-action'}>${escape(card.action.label)}${options.compact ? ' →' : ''}</a>`) +
|
|
95
|
-
(
|
|
96
|
-
|
|
97
|
-
: `<p class="${card.detail.problem ? 'problem' : 'meta'} assignment-detail">${escape(card.detail.text)}</p>`) +
|
|
98
|
-
card.problems.map(one => `<p class="problem">${escape(one)}</p>`).join('') +
|
|
99
|
-
card.diagnostics.map(one => `<p class="${one.problem ? 'problem' : 'meta'}" data-work-diagnostic="${escape(one.token)}">${escape(one.label)} · ${escape(one.detail)}</p>`).join('') +
|
|
97
|
+
(options.compact ? '' : statusDetailsHtml(card.status)) +
|
|
98
|
+
statusWhyHtml(card.status, card.reasons, card.diagnostics) +
|
|
100
99
|
(card.notices === null ? '' : `<details class="assignment-notices"><summary>${card.notices.summary}</summary>${card.notices.lines.map(one => `<p class="meta">${escape(one)}</p>`).join('')}</details>`) +
|
|
101
100
|
assignmentAttemptsHtml(assignment) + `</section>`;
|
|
102
101
|
}
|