crewforth 3.0.3 → 3.1.0-rc.1

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +211 -0
  2. package/README.md +3 -3
  3. package/VERSION +1 -1
  4. package/adopt.sh +86 -34
  5. package/kit/CLAUDE.md +1 -1
  6. package/kit/README.md +1 -1
  7. package/kit/agents/crew-commit-agent.md +7 -3
  8. package/kit/agents/crew-frontend-expert.md +4 -4
  9. package/kit/agents/crew-performance-expert.md +1 -1
  10. package/kit/agents/crew-review-agent.md +1 -1
  11. package/kit/eval/doctor.sh +57 -3
  12. package/kit/eval/golden-routing.txt +13 -0
  13. package/kit/eval/lib/git-bash.sh +11 -2
  14. package/kit/eval/parser-conformance.sh +1 -0
  15. package/kit/eval/routing-eval.sh +7 -3
  16. package/kit/eval/smoke-test.sh +2545 -214
  17. package/kit/hooks/commit-msg +1 -1
  18. package/kit/hooks/context-usage.sh +7 -1
  19. package/kit/hooks/guard-bash.sh +1608 -103
  20. package/kit/hooks/guard-commit-scan.sh +138 -19
  21. package/kit/hooks/guard-powershell.sh +89 -15
  22. package/kit/hooks/guard-write.sh +98 -22
  23. package/kit/hooks/no-bash-guard.ps1 +71 -0
  24. package/kit/hooks/pre-commit +5 -2
  25. package/kit/hooks/prompt-approval.sh +484 -0
  26. package/kit/hooks/route-hint.sh +13 -5
  27. package/kit/hooks/session-rehydrate.sh +18 -0
  28. package/kit/hooks/skill-trust.sh +17 -2
  29. package/kit/owned-blobs.tsv +43 -0
  30. package/kit/settings.json +20 -4
  31. package/kit/skills/a11y/SKILL.md +1 -0
  32. package/kit/skills/crew-doctor/SKILL.md +2 -2
  33. package/kit/skills/frontend/SKILL.md +3 -3
  34. package/kit/skills/frontend-flutter/SKILL.md +94 -0
  35. package/kit/skills/frontend-flutter/references/adaptive-and-accessible.md +55 -0
  36. package/kit/skills/frontend-flutter/references/platform-async-release.md +59 -0
  37. package/kit/skills/frontend-flutter/references/rebuilds-and-performance.md +36 -0
  38. package/kit/studio/server/hooks/studio-gate.sh +35 -1
  39. package/kit/studio/server/index.js +14 -4
  40. package/kit/studio/server/lib/graph.js +64 -3
  41. package/kit/studio/server/lib/kit.js +16 -2
  42. package/kit/studio/server/lib/permissions.js +77 -2
  43. package/kit/studio/server/lib/projects.js +60 -7
  44. package/kit/studio/server/lib/session.js +16 -2
  45. package/kit/studio/web/app.js +1765 -573
  46. package/kit/studio/web/approvals.js +124 -0
  47. package/kit/studio/web/canvas.js +932 -885
  48. package/kit/studio/web/chat.js +322 -145
  49. package/kit/studio/web/convo.js +174 -0
  50. package/kit/studio/web/dock.js +221 -0
  51. package/kit/studio/web/graph-plan.js +457 -0
  52. package/kit/studio/web/icons/builtin.svg +1 -0
  53. package/kit/studio/web/index.html +116 -50
  54. package/kit/studio/web/inspect.js +110 -0
  55. package/kit/studio/web/list-plan.js +112 -0
  56. package/kit/studio/web/list.js +157 -0
  57. package/kit/studio/web/liveness.js +66 -0
  58. package/kit/studio/web/nav.js +237 -0
  59. package/kit/studio/web/newsession.js +235 -0
  60. package/kit/studio/web/press.js +65 -0
  61. package/kit/studio/web/style.css +1281 -1014
  62. package/kit/studio/web/theme.js +43 -0
  63. package/kit/studio/web/timeline-plan.js +291 -0
  64. package/kit/studio/web/timeline.js +400 -0
  65. package/kit/studio/web/tokens.css +131 -0
  66. package/kit/studio/web/toolbar-fit.js +35 -0
  67. package/package.json +5 -4
@@ -0,0 +1,124 @@
1
+ // What is waiting for the viewer, and how long it has left.
2
+ //
3
+ // A session started here asks before every tool call: a hook parks the call, the panel shows it, and the hook's
4
+ // exit code carries the answer. Nobody answering is an answer too — the hook denies on its own when its wait runs
5
+ // out. This file works out what the dock shows from what the server sends; it touches no page.
6
+ //
7
+ // Every number here is the server's. How long the hook waits (`gateWaitSeconds`), when a request was asked
8
+ // (`askedAt`) and what time it is now (`now`) all come from the machine the hook runs on, so a viewer whose clock
9
+ // differs — a browser on the far end of a tunnel — still counts down to the moment the hook will really give up.
10
+
11
+ /** The server's clock, as seen from this page: the difference is measured each time the server says what time it is. */
12
+ export class ServerClock {
13
+ constructor() { this.offset = 0; this.synced = false; }
14
+
15
+ /** @param serverNow the server's `now` · @param localNow this page's clock when it arrived */
16
+ sync(serverNow, localNow) {
17
+ if (typeof serverNow !== 'number' || !Number.isFinite(serverNow)) return;
18
+ this.offset = serverNow - localNow;
19
+ this.synced = true;
20
+ }
21
+
22
+ now(localNow) { return localNow + this.offset; }
23
+ }
24
+
25
+ /**
26
+ * Every request waiting in every session started here, oldest first: that is the order they will time out in.
27
+ *
28
+ * @param sessions the session summaries of /api/owned
29
+ * @param nameOf sessionId -> what the navigator calls that session
30
+ */
31
+ export function queue(sessions, nameOf = () => null) {
32
+ const out = [];
33
+ for (const s of sessions ?? []) {
34
+ if (!s?.gated) continue;
35
+ for (const r of s.pendingPermissions ?? []) {
36
+ out.push({
37
+ key: `${s.sessionId}/${r.toolUseId}`,
38
+ sessionId: s.sessionId,
39
+ sessionName: nameOf(s.sessionId) ?? String(s.sessionId).slice(0, 8),
40
+ toolUseId: r.toolUseId,
41
+ toolName: r.toolName ?? 'unknown',
42
+ detail: r.detail == null ? null : String(r.detail),
43
+ agentId: r.agentId ?? null,
44
+ agentType: r.agentType ?? null,
45
+ askedAt: r.askedAt,
46
+ // Null when the server did not say. The dock then shows no countdown rather than one it made up.
47
+ waitSeconds: typeof s.gateWaitSeconds === 'number' && s.gateWaitSeconds > 0 ? s.gateWaitSeconds : null,
48
+ });
49
+ }
50
+ }
51
+ return out.sort((a, b) => (a.askedAt ?? 0) - (b.askedAt ?? 0) || a.key.localeCompare(b.key));
52
+ }
53
+
54
+ /**
55
+ * How long one request has before the hook denies it.
56
+ * @returns { known, left, fraction, expired } — `left` in whole seconds, rounded up, so it reads 1 until it is over.
57
+ */
58
+ export function remaining(item, serverNowMs) {
59
+ if (!item || item.waitSeconds == null || typeof item.askedAt !== 'number') {
60
+ return { known: false, left: null, fraction: null, expired: false };
61
+ }
62
+ const total = item.waitSeconds * 1000;
63
+ const ms = item.askedAt + total - serverNowMs;
64
+ return {
65
+ known: true,
66
+ left: Math.max(0, Math.ceil(ms / 1000)),
67
+ fraction: Math.min(1, Math.max(0, ms / total)),
68
+ expired: ms <= 0,
69
+ };
70
+ }
71
+
72
+ /** Who asked, in the words the dock uses. A call from the session itself has no agent, and says so. */
73
+ export function asker(item) {
74
+ return item.agentType ?? 'Session';
75
+ }
76
+
77
+ export const VERDICTS = {
78
+ allow: { label: 'Allow once', said: 'Allowed' },
79
+ always: { label: null, said: 'Allowed for this session' },
80
+ deny: { label: 'Deny', said: 'Denied' },
81
+ };
82
+
83
+ /** "Allow Bash this session": the button names the tool it widens the gate for. */
84
+ export function allowSessionLabel(toolName) {
85
+ return `Allow ${toolName} this session`;
86
+ }
87
+
88
+ /**
89
+ * What became of the requests that were waiting and no longer are.
90
+ *
91
+ * A request leaves the queue when the hook got its answer — from this page, from another one, or from its own
92
+ * clock. Only the last is a timeout, and it is told apart by the clock: the request vanished at or after the
93
+ * moment the hook was going to give up, and nobody here answered it.
94
+ *
95
+ * @param before the queue as it was
96
+ * @param after the queue as it is
97
+ * @param decided Map key -> verdict this page sent
98
+ * @param serverNowMs
99
+ * @param slackMs how early a disappearance still counts as the hook's own deadline (it polls; so does the panel)
100
+ */
101
+ export function settled(before, after, decided, serverNowMs, slackMs = 2500) {
102
+ const still = new Set((after ?? []).map((r) => r.key));
103
+ const out = [];
104
+ for (const r of before ?? []) {
105
+ if (still.has(r.key)) continue;
106
+ const verdict = decided?.get(r.key) ?? null;
107
+ let outcome;
108
+ if (verdict === 'deny') outcome = 'denied';
109
+ else if (verdict === 'allow') outcome = 'allowed';
110
+ else if (verdict === 'always') outcome = 'allowed-session';
111
+ else if (r.waitSeconds != null && serverNowMs >= r.askedAt + r.waitSeconds * 1000 - slackMs) outcome = 'timed-out';
112
+ else outcome = 'answered-elsewhere';
113
+ out.push({ ...r, outcome, at: serverNowMs });
114
+ }
115
+ return out;
116
+ }
117
+
118
+ export const OUTCOME_WORD = {
119
+ allowed: 'Allowed',
120
+ 'allowed-session': 'Allowed for this session',
121
+ denied: 'Denied',
122
+ 'timed-out': 'Timed out — denied',
123
+ 'answered-elsewhere': 'Answered elsewhere',
124
+ };