@skrr-ai/cli 0.1.36 → 0.1.38
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 +17 -4
- package/bin/dev-fallback.js +167 -4
- package/dist/base-command.js +46 -2
- package/dist/commands/agents/actions/create.d.ts +5 -0
- package/dist/commands/agents/actions/create.js +42 -5
- package/dist/commands/agents/actions/delete.d.ts +1 -0
- package/dist/commands/agents/actions/delete.js +7 -0
- package/dist/commands/agents/actions/update.d.ts +5 -0
- package/dist/commands/agents/actions/update.js +27 -1
- package/dist/commands/agents/api-actions/add.d.ts +41 -0
- package/dist/commands/agents/api-actions/add.js +205 -0
- package/dist/commands/agents/api-actions/index.d.ts +39 -0
- package/dist/commands/agents/api-actions/index.js +103 -0
- package/dist/commands/agents/api-actions/remove.d.ts +14 -0
- package/dist/commands/agents/api-actions/remove.js +36 -0
- package/dist/commands/agents/avatar/default/apply.d.ts +25 -0
- package/dist/commands/agents/avatar/default/apply.js +56 -0
- package/dist/commands/agents/avatar/default/index.d.ts +29 -0
- package/dist/commands/agents/avatar/default/index.js +61 -0
- package/dist/commands/agents/avatar/default/set.d.ts +24 -0
- package/dist/commands/agents/avatar/default/set.js +120 -0
- package/dist/commands/agents/avatar/library/delete.d.ts +20 -0
- package/dist/commands/agents/avatar/library/delete.js +47 -0
- package/dist/commands/agents/avatar/library/index.d.ts +16 -0
- package/dist/commands/agents/avatar/library/index.js +56 -0
- package/dist/commands/agents/avatar/presets.d.ts +19 -0
- package/dist/commands/agents/avatar/presets.js +56 -0
- package/dist/commands/agents/avatar/upload.js +7 -1
- package/dist/commands/agents/categories/create.d.ts +14 -0
- package/dist/commands/agents/categories/create.js +46 -0
- package/dist/commands/agents/categories/delete.d.ts +13 -0
- package/dist/commands/agents/categories/delete.js +41 -0
- package/dist/commands/agents/categories/index.d.ts +36 -0
- package/dist/commands/agents/categories/index.js +135 -0
- package/dist/commands/agents/categories/update.d.ts +22 -0
- package/dist/commands/agents/categories/update.js +59 -0
- package/dist/commands/agents/chat.d.ts +21 -0
- package/dist/commands/agents/chat.js +94 -7
- package/dist/commands/agents/create.d.ts +10 -0
- package/dist/commands/agents/create.js +64 -3
- package/dist/commands/agents/ensure-default.d.ts +27 -0
- package/dist/commands/agents/ensure-default.js +65 -0
- package/dist/commands/agents/list.d.ts +15 -0
- package/dist/commands/agents/list.js +31 -1
- package/dist/commands/agents/mute.d.ts +16 -0
- package/dist/commands/agents/mute.js +38 -0
- package/dist/commands/agents/pin.d.ts +16 -0
- package/dist/commands/agents/pin.js +38 -0
- package/dist/commands/agents/scene-background/upload.d.ts +30 -0
- package/dist/commands/agents/scene-background/upload.js +97 -0
- package/dist/commands/agents/show.js +13 -0
- package/dist/commands/agents/tools/auth.d.ts +19 -0
- package/dist/commands/agents/tools/auth.js +41 -0
- package/dist/commands/agents/tools/calls.d.ts +36 -0
- package/dist/commands/agents/tools/calls.js +117 -0
- package/dist/commands/agents/tools/index.d.ts +20 -0
- package/dist/commands/agents/tools/index.js +77 -0
- package/dist/commands/agents/unmute.d.ts +16 -0
- package/dist/commands/agents/unmute.js +38 -0
- package/dist/commands/agents/unpin.d.ts +16 -0
- package/dist/commands/agents/unpin.js +38 -0
- package/dist/commands/agents/update.d.ts +12 -0
- package/dist/commands/agents/update.js +74 -3
- package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
- package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
- package/dist/commands/convos/messages.d.ts +13 -0
- package/dist/commands/convos/messages.js +40 -16
- package/dist/commands/daemon/index.js +8 -1
- package/dist/commands/daemon/logs.d.ts +26 -0
- package/dist/commands/daemon/logs.js +56 -0
- package/dist/commands/daemon/uninstall.js +1 -1
- package/dist/commands/followups/act.d.ts +22 -0
- package/dist/commands/followups/act.js +103 -0
- package/dist/commands/followups/cancel.d.ts +20 -0
- package/dist/commands/followups/cancel.js +50 -0
- package/dist/commands/followups/checkin.d.ts +27 -0
- package/dist/commands/followups/checkin.js +107 -0
- package/dist/commands/followups/list.d.ts +24 -0
- package/dist/commands/followups/list.js +90 -0
- package/dist/commands/followups/observability.d.ts +23 -0
- package/dist/commands/followups/observability.js +114 -0
- package/dist/commands/followups/remind.d.ts +26 -0
- package/dist/commands/followups/remind.js +106 -0
- package/dist/commands/followups/reschedule.d.ts +15 -0
- package/dist/commands/followups/reschedule.js +48 -0
- package/dist/commands/followups/resolve.d.ts +23 -0
- package/dist/commands/followups/resolve.js +54 -0
- package/dist/commands/followups/show.d.ts +12 -0
- package/dist/commands/followups/show.js +46 -0
- package/dist/commands/followups/watch.d.ts +51 -0
- package/dist/commands/followups/watch.js +211 -0
- package/dist/commands/goals/labels/add.js +19 -2
- package/dist/commands/goals/labels/remove.js +22 -2
- package/dist/commands/harnesses/forget.js +28 -0
- package/dist/commands/harnesses/install.d.ts +41 -0
- package/dist/commands/harnesses/install.js +165 -0
- package/dist/commands/harnesses/installers.d.ts +45 -0
- package/dist/commands/harnesses/installers.js +155 -0
- package/dist/commands/harnesses/list.d.ts +82 -1
- package/dist/commands/harnesses/list.js +104 -12
- package/dist/commands/harnesses/models.js +10 -1
- package/dist/commands/harnesses/show.d.ts +15 -0
- package/dist/commands/harnesses/show.js +44 -3
- package/dist/commands/harnesses/update.js +1 -1
- package/dist/commands/harnesses/usage.d.ts +25 -0
- package/dist/commands/harnesses/usage.js +24 -2
- package/dist/commands/instructions/status.js +13 -1
- package/dist/commands/labels/list.d.ts +24 -1
- package/dist/commands/labels/list.js +62 -19
- package/dist/commands/machines/dedicated/catalog.js +4 -15
- package/dist/commands/machines/dedicated/create.d.ts +8 -0
- package/dist/commands/machines/dedicated/create.js +58 -1
- package/dist/commands/machines/dedicated/destroy.js +18 -4
- package/dist/commands/machines/dedicated/list.js +9 -6
- package/dist/commands/machines/hosted/list.js +12 -0
- package/dist/commands/machines/list.js +11 -0
- package/dist/commands/machines/show.d.ts +1 -0
- package/dist/commands/machines/show.js +40 -11
- package/dist/commands/memory/governance/index.d.ts +21 -0
- package/dist/commands/memory/governance/index.js +64 -0
- package/dist/commands/memory/governance/set.d.ts +24 -0
- package/dist/commands/memory/governance/set.js +95 -0
- package/dist/commands/memory/provenance.d.ts +18 -0
- package/dist/commands/memory/provenance.js +66 -0
- package/dist/commands/profiles/list.d.ts +23 -0
- package/dist/commands/profiles/list.js +96 -0
- package/dist/commands/search/skills.d.ts +11 -1
- package/dist/commands/search/skills.js +26 -8
- package/dist/commands/skills/details.d.ts +2 -0
- package/dist/commands/skills/details.js +24 -1
- package/dist/commands/skills/import-as-actions.d.ts +32 -0
- package/dist/commands/skills/import-as-actions.js +175 -0
- package/dist/commands/skills/install.d.ts +2 -0
- package/dist/commands/skills/install.js +24 -1
- package/dist/commands/skills/invocable.js +6 -2
- package/dist/commands/skills/local.d.ts +40 -0
- package/dist/commands/skills/local.js +160 -0
- package/dist/commands/skills/search.d.ts +11 -1
- package/dist/commands/skills/search.js +29 -8
- package/dist/commands/spaces/actions/add.js +23 -5
- package/dist/commands/spaces/actions/available.js +3 -0
- package/dist/commands/spaces/actions/list.d.ts +5 -0
- package/dist/commands/spaces/actions/list.js +49 -3
- package/dist/commands/spaces/actions/run.d.ts +28 -0
- package/dist/commands/spaces/actions/run.js +146 -0
- package/dist/commands/spaces/actions/update.js +27 -5
- package/dist/commands/spaces/create.d.ts +3 -0
- package/dist/commands/spaces/create.js +32 -2
- package/dist/commands/spaces/label-summaries.d.ts +27 -0
- package/dist/commands/spaces/label-summaries.js +64 -0
- package/dist/commands/spaces/labels/add.js +19 -2
- package/dist/commands/spaces/labels/remove.js +22 -2
- package/dist/commands/spaces/show.js +2 -0
- package/dist/commands/spaces/task-rollups.d.ts +5 -0
- package/dist/commands/spaces/task-rollups.js +15 -4
- package/dist/commands/spaces/update.d.ts +5 -0
- package/dist/commands/spaces/update.js +41 -4
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +11 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +105 -0
- package/dist/commands/tasks/actions/attach.js +32 -7
- package/dist/commands/tasks/complete.d.ts +1 -0
- package/dist/commands/tasks/complete.js +21 -1
- package/dist/commands/tasks/create.d.ts +3 -0
- package/dist/commands/tasks/create.js +34 -2
- package/dist/commands/tasks/events/append.js +29 -6
- package/dist/commands/tasks/expectations/assess.d.ts +12 -0
- package/dist/commands/tasks/expectations/assess.js +123 -14
- package/dist/commands/tasks/expectations.js +95 -17
- package/dist/commands/tasks/follow-up.js +2 -2
- package/dist/commands/tasks/labels/attach.js +20 -3
- package/dist/commands/tasks/labels/detach.js +21 -3
- package/dist/commands/tasks/lifecycle/attach.d.ts +14 -0
- package/dist/commands/tasks/lifecycle/attach.js +47 -0
- package/dist/commands/tasks/lifecycle/definitions.d.ts +11 -0
- package/dist/commands/tasks/lifecycle/definitions.js +59 -0
- package/dist/commands/tasks/lifecycle/detach.d.ts +19 -0
- package/dist/commands/tasks/lifecycle/detach.js +72 -0
- package/dist/commands/tasks/lifecycle/history.d.ts +13 -0
- package/dist/commands/tasks/lifecycle/history.js +38 -0
- package/dist/commands/tasks/lifecycle/migrate.d.ts +14 -0
- package/dist/commands/tasks/lifecycle/migrate.js +68 -0
- package/dist/commands/tasks/lifecycle/reconcile.d.ts +11 -0
- package/dist/commands/tasks/lifecycle/reconcile.js +27 -0
- package/dist/commands/tasks/lifecycle/reopen.d.ts +15 -0
- package/dist/commands/tasks/lifecycle/reopen.js +71 -0
- package/dist/commands/tasks/lifecycle/show.d.ts +11 -0
- package/dist/commands/tasks/lifecycle/show.js +27 -0
- package/dist/commands/tasks/lifecycle/start.d.ts +12 -0
- package/dist/commands/tasks/lifecycle/start.js +30 -0
- package/dist/commands/tasks/lifecycle/transition.d.ts +15 -0
- package/dist/commands/tasks/lifecycle/transition.js +109 -0
- package/dist/commands/tasks/list.js +16 -9
- package/dist/commands/tasks/move.d.ts +1 -0
- package/dist/commands/tasks/move.js +29 -0
- package/dist/commands/tasks/result/submit.js +11 -1
- package/dist/commands/tasks/review-queue.d.ts +44 -0
- package/dist/commands/tasks/review-queue.js +314 -0
- package/dist/commands/tasks/show.d.ts +11 -0
- package/dist/commands/tasks/show.js +139 -4
- package/dist/commands/tasks/trust.js +7 -2
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +83 -0
- package/dist/commands/tasks/updates/add.js +1 -1
- package/dist/commands/token/list.d.ts +22 -0
- package/dist/commands/token/list.js +68 -0
- package/dist/commands/token/revoke.d.ts +23 -0
- package/dist/commands/token/revoke.js +53 -0
- package/dist/lib/agent-actions.d.ts +92 -11
- package/dist/lib/agent-actions.js +171 -25
- package/dist/lib/agent-config.d.ts +11 -0
- package/dist/lib/agent-config.js +85 -3
- package/dist/lib/agent-pin-mute.d.ts +29 -0
- package/dist/lib/agent-pin-mute.js +30 -0
- package/dist/lib/agent-visual-refs.d.ts +33 -0
- package/dist/lib/agent-visual-refs.js +267 -0
- package/dist/lib/agentic-stream.d.ts +6 -0
- package/dist/lib/agentic-stream.js +34 -3
- package/dist/lib/assignee-resolver.d.ts +1 -1
- package/dist/lib/assignee-resolver.js +8 -0
- package/dist/lib/bulk-task-targets.d.ts +12 -0
- package/dist/lib/bulk-task-targets.js +34 -1
- package/dist/lib/cli-installers.d.ts +130 -0
- package/dist/lib/cli-installers.js +145 -0
- package/dist/lib/command-miss.js +55 -7
- package/dist/lib/dedicated-lease-command.d.ts +13 -0
- package/dist/lib/dedicated-lease-command.js +22 -1
- package/dist/lib/dedicated-machines.d.ts +95 -1
- package/dist/lib/dedicated-machines.js +236 -2
- package/dist/lib/failed-lookup.d.ts +3 -0
- package/dist/lib/failed-lookup.js +40 -0
- package/dist/lib/first-party-harness-agent.js +16 -2
- package/dist/lib/first-party-harness-doctor.d.ts +15 -0
- package/dist/lib/first-party-harness-doctor.js +126 -27
- package/dist/lib/followups.d.ts +115 -0
- package/dist/lib/followups.js +127 -0
- package/dist/lib/harnesses.d.ts +8 -0
- package/dist/lib/instruction-input.d.ts +19 -1
- package/dist/lib/instruction-input.js +52 -1
- package/dist/lib/label-ref.d.ts +95 -0
- package/dist/lib/label-ref.js +163 -0
- package/dist/lib/label-scope.d.ts +17 -2
- package/dist/lib/label-scope.js +27 -7
- package/dist/lib/local-skills.d.ts +98 -0
- package/dist/lib/local-skills.js +195 -0
- package/dist/lib/machines.d.ts +10 -10
- package/dist/lib/machines.js +23 -1
- package/dist/lib/search-query.d.ts +19 -0
- package/dist/lib/search-query.js +35 -0
- package/dist/lib/space-custom-actions.d.ts +72 -0
- package/dist/lib/space-custom-actions.js +117 -0
- package/dist/lib/space-resolver.d.ts +1 -21
- package/dist/lib/space-resolver.js +19 -10
- package/dist/lib/task-closure.d.ts +33 -0
- package/dist/lib/task-closure.js +104 -0
- package/dist/lib/task-lifecycle-output.d.ts +14 -0
- package/dist/lib/task-lifecycle-output.js +209 -0
- package/dist/lib/task-ref-resolver.d.ts +1 -17
- package/dist/lib/task-ref-resolver.js +18 -8
- package/dist/lib/tasks.d.ts +3 -0
- package/dist/lib/tasks.js +40 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5559 -4897
- package/oclif.manifest.json +33890 -28690
- package/package.json +24 -6
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { withQuery } from './triggers';
|
|
2
|
+
type Query = Parameters<typeof withQuery>[1];
|
|
3
|
+
type Json = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* The `id` argument every follow-up command that addresses one takes.
|
|
6
|
+
*
|
|
7
|
+
* oclif treats an empty string as a PRESENT required argument, so a scripted
|
|
8
|
+
* `skrr followups show "$ID"` with an empty variable built `/api/followups/` —
|
|
9
|
+
* the LIST route — and answered with the list's requirement, "agentId is
|
|
10
|
+
* required unless allAgents is set", naming two flags `show` does not have
|
|
11
|
+
* (OSK-9676). Refused at parse time instead, so it renders where a missing
|
|
12
|
+
* argument does, with usage, before any request is sent.
|
|
13
|
+
*/
|
|
14
|
+
export declare const followUpIdArg: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
15
|
+
export { FOLLOWUP_KINDS, FOLLOWUP_STATES, FOLLOWUP_RESOLVE_OUTCOMES, type FollowUpKind, } from '@skrr-ai/data-provider';
|
|
16
|
+
export declare const WATCH_WAIT_KINDS: readonly ["clock", "event", "poll_until"];
|
|
17
|
+
export interface FollowUpRow {
|
|
18
|
+
id: string;
|
|
19
|
+
kind?: string;
|
|
20
|
+
form?: string | null;
|
|
21
|
+
producer?: string;
|
|
22
|
+
why?: string;
|
|
23
|
+
/** A reminder's own words. */
|
|
24
|
+
content?: string | null;
|
|
25
|
+
intentKey?: string;
|
|
26
|
+
subject?: {
|
|
27
|
+
type?: string;
|
|
28
|
+
externalKey?: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
} | null;
|
|
32
|
+
agentId?: string | null;
|
|
33
|
+
state?: string;
|
|
34
|
+
terminal?: string | null;
|
|
35
|
+
terminalAt?: string | null;
|
|
36
|
+
terminalReason?: string | null;
|
|
37
|
+
escalatedInitiativeId?: string | null;
|
|
38
|
+
capsule?: unknown;
|
|
39
|
+
nextRunAt?: string | null;
|
|
40
|
+
deadlineAt?: string | null;
|
|
41
|
+
createdAt?: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface FollowUpCreateResponse {
|
|
45
|
+
followup: FollowUpRow | null;
|
|
46
|
+
reused?: boolean;
|
|
47
|
+
dropped?: false | 'budget';
|
|
48
|
+
proposal?: boolean;
|
|
49
|
+
budget?: Json;
|
|
50
|
+
fallbackTriggerId?: string | null;
|
|
51
|
+
}
|
|
52
|
+
export interface FollowUpObservability {
|
|
53
|
+
generatedAt?: string;
|
|
54
|
+
agents?: Array<{
|
|
55
|
+
agentId: string;
|
|
56
|
+
kinds: Array<{
|
|
57
|
+
kind: string;
|
|
58
|
+
created: number;
|
|
59
|
+
armed: number;
|
|
60
|
+
proposed: number;
|
|
61
|
+
settled: number;
|
|
62
|
+
terminal: Record<string, number>;
|
|
63
|
+
escalationRate: number | null;
|
|
64
|
+
fires: number;
|
|
65
|
+
budget: {
|
|
66
|
+
pool: string | null;
|
|
67
|
+
used: number;
|
|
68
|
+
limit: number | null;
|
|
69
|
+
} | null;
|
|
70
|
+
silenceRate: number | null;
|
|
71
|
+
silentFires: number | null;
|
|
72
|
+
}>;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
export type ResolvedFollowUpAgent = {
|
|
76
|
+
id: string;
|
|
77
|
+
/**
|
|
78
|
+
* True when the identity came from the session binding rather than a flag —
|
|
79
|
+
* a follow-up declared inside an agent's own turn is `agent_in_turn`; one
|
|
80
|
+
* declared by a human at a shell is `user_explicit`. The producer field is
|
|
81
|
+
* provenance, not authority, and this is the only honest signal a CLI has.
|
|
82
|
+
*/
|
|
83
|
+
boundToSession: boolean;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Which agent a follow-up belongs to.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately NOT `resolveAgentIdentity`: that resolver's last step picks
|
|
89
|
+
* "the only visible agent", and a follow-up bound to the wrong agent wakes
|
|
90
|
+
* the wrong agent in the wrong main chat. The only honest sources are an
|
|
91
|
+
* explicit `--agent` and the session binding the runtime already proves
|
|
92
|
+
* (`OVERSKY_SESSION_AGENT_ID`, or the legacy `OVERSKY_AGENT_ID` a manual
|
|
93
|
+
* local runtime sets). Anything else throws AGENT_CONTEXT_AMBIGUOUS and
|
|
94
|
+
* names the fix rather than guessing.
|
|
95
|
+
*/
|
|
96
|
+
export declare function resolveFollowUpAgent(explicitAgentId?: string, env?: Record<string, string | undefined>): ResolvedFollowUpAgent;
|
|
97
|
+
/** A `type:externalKey` subject ref → the subject block the API wants. */
|
|
98
|
+
export declare function parseSubjectRef(ref: string): {
|
|
99
|
+
type: string;
|
|
100
|
+
externalKey: string;
|
|
101
|
+
};
|
|
102
|
+
export declare const followupsApi: {
|
|
103
|
+
create: (body: Json) => Promise<FollowUpCreateResponse>;
|
|
104
|
+
list: (query?: Query) => Promise<{
|
|
105
|
+
followups: FollowUpRow[];
|
|
106
|
+
}>;
|
|
107
|
+
observability: (query?: Query) => Promise<FollowUpObservability>;
|
|
108
|
+
show: (id: string) => Promise<{
|
|
109
|
+
followup: FollowUpRow;
|
|
110
|
+
}>;
|
|
111
|
+
resolve: (id: string, body: Json) => Promise<Json>;
|
|
112
|
+
cancel: (id: string, body?: Json) => Promise<Json>;
|
|
113
|
+
reschedule: (id: string, body: Json) => Promise<Json>;
|
|
114
|
+
};
|
|
115
|
+
export declare function followupSummaryLine(row: FollowUpRow): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.followupsApi = exports.WATCH_WAIT_KINDS = exports.FOLLOWUP_RESOLVE_OUTCOMES = exports.FOLLOWUP_STATES = exports.FOLLOWUP_KINDS = exports.followUpIdArg = void 0;
|
|
4
|
+
exports.resolveFollowUpAgent = resolveFollowUpAgent;
|
|
5
|
+
exports.parseSubjectRef = parseSubjectRef;
|
|
6
|
+
exports.followupSummaryLine = followupSummaryLine;
|
|
7
|
+
/**
|
|
8
|
+
* followups.ts — the `skrr followups` domain lib.
|
|
9
|
+
*
|
|
10
|
+
* A follow-up is ONE resource with four kinds (`reminder`, `watch`, `checkin`,
|
|
11
|
+
* `act`), declared by intent and derived into mechanism entirely server-side
|
|
12
|
+
* (`FollowUps/` + `docs/architecture/followup-service-2026-09-15.md`). The CLI
|
|
13
|
+
* is the universal substrate — a daemon-run local harness reaches the platform
|
|
14
|
+
* only through `skrr` — so every verb the API exposes lives here, and commands
|
|
15
|
+
* own flags only. The `followup_*` MCP tools wrap the same surface.
|
|
16
|
+
*
|
|
17
|
+
* What this file deliberately does NOT contain: executor modes, trigger kinds,
|
|
18
|
+
* destination logic, or budget policy. A caller names `kind` + `subject` +
|
|
19
|
+
* `why` and the server derives the rest; a second derivation here would be
|
|
20
|
+
* exactly the drift the service exists to kill.
|
|
21
|
+
*/
|
|
22
|
+
const core_1 = require("@oclif/core");
|
|
23
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
24
|
+
const runtime_context_1 = require("./runtime-context");
|
|
25
|
+
const triggers_1 = require("./triggers");
|
|
26
|
+
const agent_resolver_1 = require("./agent-resolver");
|
|
27
|
+
const MOUNT = '/api/followups';
|
|
28
|
+
const base = (id) => `${MOUNT}/${encodeURIComponent(id)}`;
|
|
29
|
+
/**
|
|
30
|
+
* The `id` argument every follow-up command that addresses one takes.
|
|
31
|
+
*
|
|
32
|
+
* oclif treats an empty string as a PRESENT required argument, so a scripted
|
|
33
|
+
* `skrr followups show "$ID"` with an empty variable built `/api/followups/` —
|
|
34
|
+
* the LIST route — and answered with the list's requirement, "agentId is
|
|
35
|
+
* required unless allAgents is set", naming two flags `show` does not have
|
|
36
|
+
* (OSK-9676). Refused at parse time instead, so it renders where a missing
|
|
37
|
+
* argument does, with usage, before any request is sent.
|
|
38
|
+
*/
|
|
39
|
+
exports.followUpIdArg = core_1.Args.string({
|
|
40
|
+
description: 'Follow-up (trigger) id',
|
|
41
|
+
required: true,
|
|
42
|
+
ignoreStdin: true,
|
|
43
|
+
parse: async (input) => {
|
|
44
|
+
const id = input.trim();
|
|
45
|
+
if (!id)
|
|
46
|
+
throw new Error('id is empty — pass the follow-up id (see `skrr followups list`)');
|
|
47
|
+
return id;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
/* ------------------------------------------------------------------ *
|
|
51
|
+
* Vocabulary (client-side validation only — the server is the authority) *
|
|
52
|
+
* ------------------------------------------------------------------ */
|
|
53
|
+
// Owned by `@skrr-ai/data-provider`, which the server validates against. These
|
|
54
|
+
// were declared a second time here, and the state list is where the two copies
|
|
55
|
+
// disagreed.
|
|
56
|
+
var data_provider_2 = require("@skrr-ai/data-provider");
|
|
57
|
+
Object.defineProperty(exports, "FOLLOWUP_KINDS", { enumerable: true, get: function () { return data_provider_2.FOLLOWUP_KINDS; } });
|
|
58
|
+
Object.defineProperty(exports, "FOLLOWUP_STATES", { enumerable: true, get: function () { return data_provider_2.FOLLOWUP_STATES; } });
|
|
59
|
+
Object.defineProperty(exports, "FOLLOWUP_RESOLVE_OUTCOMES", { enumerable: true, get: function () { return data_provider_2.FOLLOWUP_RESOLVE_OUTCOMES; } });
|
|
60
|
+
exports.WATCH_WAIT_KINDS = ['clock', 'event', 'poll_until'];
|
|
61
|
+
/**
|
|
62
|
+
* Which agent a follow-up belongs to.
|
|
63
|
+
*
|
|
64
|
+
* Deliberately NOT `resolveAgentIdentity`: that resolver's last step picks
|
|
65
|
+
* "the only visible agent", and a follow-up bound to the wrong agent wakes
|
|
66
|
+
* the wrong agent in the wrong main chat. The only honest sources are an
|
|
67
|
+
* explicit `--agent` and the session binding the runtime already proves
|
|
68
|
+
* (`OVERSKY_SESSION_AGENT_ID`, or the legacy `OVERSKY_AGENT_ID` a manual
|
|
69
|
+
* local runtime sets). Anything else throws AGENT_CONTEXT_AMBIGUOUS and
|
|
70
|
+
* names the fix rather than guessing.
|
|
71
|
+
*/
|
|
72
|
+
function resolveFollowUpAgent(explicitAgentId, env = process.env) {
|
|
73
|
+
const runtime = (0, runtime_context_1.readRuntimeContext)(env);
|
|
74
|
+
const sessionAgent = runtime.agent?.source === 'OVERSKY_SESSION_AGENT_ID' ||
|
|
75
|
+
runtime.agent?.source === 'OVERSKY_AGENT_ID'
|
|
76
|
+
? runtime.agent
|
|
77
|
+
: undefined;
|
|
78
|
+
if (sessionAgent) {
|
|
79
|
+
if (explicitAgentId && explicitAgentId !== sessionAgent.id) {
|
|
80
|
+
throw new agent_resolver_1.AgentResolutionError({
|
|
81
|
+
code: 'AGENT_CONTEXT_CONFLICT',
|
|
82
|
+
message: 'This local runtime is bound to a session agent. Do not override it with another agent ID.',
|
|
83
|
+
suggestion: 'Use the bound session agent, or start a new daemon session for a different agent.',
|
|
84
|
+
details: { explicitAgentId, sessionAgentId: sessionAgent.id, source: sessionAgent.source },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return { id: sessionAgent.id, boundToSession: true };
|
|
88
|
+
}
|
|
89
|
+
if (explicitAgentId)
|
|
90
|
+
return { id: explicitAgentId, boundToSession: false };
|
|
91
|
+
throw new agent_resolver_1.AgentResolutionError({
|
|
92
|
+
code: 'AGENT_CONTEXT_AMBIGUOUS',
|
|
93
|
+
message: 'No agent: this session is not bound to one, so name the agent these follow-ups are for.',
|
|
94
|
+
suggestion: 'Pass `--agent <agent-id>`, run inside a daemon session that sets OVERSKY_SESSION_AGENT_ID, or use `--all-agents` where listing is the intent.',
|
|
95
|
+
details: { runtimeKind: runtime.kind },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** A `type:externalKey` subject ref → the subject block the API wants. */
|
|
99
|
+
function parseSubjectRef(ref) {
|
|
100
|
+
const idx = ref.indexOf(':');
|
|
101
|
+
if (idx <= 0 || idx === ref.length - 1) {
|
|
102
|
+
throw new Error(`--subject must be "type:externalKey" (e.g. task:OSK-42, conversation:conv_123), got "${ref}"`);
|
|
103
|
+
}
|
|
104
|
+
return { type: ref.slice(0, idx), externalKey: ref.slice(idx + 1) };
|
|
105
|
+
}
|
|
106
|
+
/* ------------------------------------------------------------------ *
|
|
107
|
+
* API *
|
|
108
|
+
* ------------------------------------------------------------------ */
|
|
109
|
+
exports.followupsApi = {
|
|
110
|
+
create: (body) => data_provider_1.request.post(MOUNT, body),
|
|
111
|
+
list: (query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(MOUNT, query)),
|
|
112
|
+
observability: (query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${MOUNT}/observability`, query)),
|
|
113
|
+
show: (id) => data_provider_1.request.get(base(id)),
|
|
114
|
+
resolve: (id, body) => data_provider_1.request.post(`${base(id)}/resolve`, body),
|
|
115
|
+
cancel: (id, body = {}) => data_provider_1.request.post(`${base(id)}/cancel`, body),
|
|
116
|
+
reschedule: (id, body) => data_provider_1.request.post(`${base(id)}/reschedule`, body),
|
|
117
|
+
};
|
|
118
|
+
/* ------------------------------------------------------------------ *
|
|
119
|
+
* Rendering *
|
|
120
|
+
* ------------------------------------------------------------------ */
|
|
121
|
+
const fmtWhen = (iso) => (iso ? iso.slice(0, 16).replace('T', ' ') + 'Z' : '—');
|
|
122
|
+
function followupSummaryLine(row) {
|
|
123
|
+
const subject = row.subject ? `${row.subject.type}:${row.subject.externalKey}` : '—';
|
|
124
|
+
const kindForm = row.form ? `${row.kind}/${row.form}` : (row.kind ?? '—');
|
|
125
|
+
const when = row.state === 'settled' ? `settled:${row.terminal ?? '?'}` : `next:${fmtWhen(row.nextRunAt)}`;
|
|
126
|
+
return `${row.id} [${row.state ?? '?'}] ${kindForm} ${subject} ${when}`;
|
|
127
|
+
}
|
package/dist/lib/harnesses.d.ts
CHANGED
|
@@ -42,6 +42,14 @@ export interface Harness {
|
|
|
42
42
|
_id: string;
|
|
43
43
|
provider: string;
|
|
44
44
|
name?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The name everyone else sees, when YOUR alias is covering it. `name` is the
|
|
47
|
+
* effective one. Present only while an alias is actually shadowing a
|
|
48
|
+
* different value, so a reader can tell "no alias" from "alias equals the
|
|
49
|
+
* shared name".
|
|
50
|
+
*/
|
|
51
|
+
sharedName?: string;
|
|
52
|
+
aliased?: boolean;
|
|
45
53
|
status?: string;
|
|
46
54
|
harnessLocation?: 'local' | 'cloud';
|
|
47
55
|
daemonId?: string;
|
|
@@ -50,11 +50,29 @@ export declare function resolveBundleLabel(bundleId: string | undefined, names:
|
|
|
50
50
|
* the daemon is genuinely gone. A remedy that 409s is worse than no remedy — it
|
|
51
51
|
* spends the reader's attention and returns them to where they started.
|
|
52
52
|
*/
|
|
53
|
+
/**
|
|
54
|
+
* A daemon id is `daemon_<hash>_<source>[_<profile>]`, where the hash covers
|
|
55
|
+
* the MACHINE and the cwd and `<source>` is the supervision mode
|
|
56
|
+
* (`cli`, `launchd`, `electron`, …). So two ids that differ only in that
|
|
57
|
+
* segment describe the SAME machine and directory, started a different way.
|
|
58
|
+
*
|
|
59
|
+
* That matters because switching how the daemon is started — the desktop app
|
|
60
|
+
* installs a launchd agent, `skrr daemon start` runs a cli one — mints a new
|
|
61
|
+
* id and orphans every managed-instruction target pinned to the old one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseDaemonId(daemonId: string): {
|
|
64
|
+
place: string;
|
|
65
|
+
source: string;
|
|
66
|
+
profile: string;
|
|
67
|
+
} | null;
|
|
68
|
+
/** The live daemon that is this target's machine and directory, started differently. */
|
|
69
|
+
export declare function sameMachineDaemon(targetDaemonId: string, liveDaemonIds: string[]): string | null;
|
|
53
70
|
export declare function describeTargetSync(target: {
|
|
54
71
|
status: string;
|
|
72
|
+
daemonId?: string;
|
|
55
73
|
lastError?: string;
|
|
56
74
|
lastSyncedAt?: string;
|
|
57
75
|
lastAttemptedAt?: string;
|
|
58
76
|
updatedAt?: string;
|
|
59
77
|
driftCount?: number;
|
|
60
|
-
}, now?: number): string;
|
|
78
|
+
}, now?: number, liveDaemonIds?: string[]): string;
|
|
@@ -4,6 +4,8 @@ exports.readInstructionBody = readInstructionBody;
|
|
|
4
4
|
exports.formatSyncStatus = formatSyncStatus;
|
|
5
5
|
exports.buildBundleNameIndex = buildBundleNameIndex;
|
|
6
6
|
exports.resolveBundleLabel = resolveBundleLabel;
|
|
7
|
+
exports.parseDaemonId = parseDaemonId;
|
|
8
|
+
exports.sameMachineDaemon = sameMachineDaemon;
|
|
7
9
|
exports.describeTargetSync = describeTargetSync;
|
|
8
10
|
const promises_1 = require("node:fs/promises");
|
|
9
11
|
async function readStdin() {
|
|
@@ -93,7 +95,39 @@ const PENDING_IS_SUSPECT_AFTER_MS = 24 * 60 * 60 * 1000;
|
|
|
93
95
|
* the daemon is genuinely gone. A remedy that 409s is worse than no remedy — it
|
|
94
96
|
* spends the reader's attention and returns them to where they started.
|
|
95
97
|
*/
|
|
96
|
-
|
|
98
|
+
/**
|
|
99
|
+
* A daemon id is `daemon_<hash>_<source>[_<profile>]`, where the hash covers
|
|
100
|
+
* the MACHINE and the cwd and `<source>` is the supervision mode
|
|
101
|
+
* (`cli`, `launchd`, `electron`, …). So two ids that differ only in that
|
|
102
|
+
* segment describe the SAME machine and directory, started a different way.
|
|
103
|
+
*
|
|
104
|
+
* That matters because switching how the daemon is started — the desktop app
|
|
105
|
+
* installs a launchd agent, `skrr daemon start` runs a cli one — mints a new
|
|
106
|
+
* id and orphans every managed-instruction target pinned to the old one.
|
|
107
|
+
*/
|
|
108
|
+
function parseDaemonId(daemonId) {
|
|
109
|
+
const parts = String(daemonId || '').split('_');
|
|
110
|
+
if (parts.length < 3 || parts[0] !== 'daemon')
|
|
111
|
+
return null;
|
|
112
|
+
return { place: parts[1], source: parts[2], profile: parts.slice(3).join('_') };
|
|
113
|
+
}
|
|
114
|
+
/** The live daemon that is this target's machine and directory, started differently. */
|
|
115
|
+
function sameMachineDaemon(targetDaemonId, liveDaemonIds) {
|
|
116
|
+
const target = parseDaemonId(targetDaemonId);
|
|
117
|
+
if (!target)
|
|
118
|
+
return null;
|
|
119
|
+
for (const live of liveDaemonIds) {
|
|
120
|
+
if (live === targetDaemonId)
|
|
121
|
+
continue;
|
|
122
|
+
const other = parseDaemonId(live);
|
|
123
|
+
if (!other)
|
|
124
|
+
continue;
|
|
125
|
+
if (other.place === target.place && other.profile === target.profile)
|
|
126
|
+
return live;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
function describeTargetSync(target, now = Date.now(), liveDaemonIds = []) {
|
|
97
131
|
const base = formatSyncStatus(target.status, target.lastError);
|
|
98
132
|
// Say when skrr has overwritten something you typed.
|
|
99
133
|
//
|
|
@@ -131,5 +165,22 @@ function describeTargetSync(target, now = Date.now()) {
|
|
|
131
165
|
// remedy off the end — the same truncation e51abf3362 exists to prevent. A
|
|
132
166
|
// stalled target is not reconciling, so its drift is historical and the
|
|
133
167
|
// remedy is the actionable half; the count reappears once it is unstuck.
|
|
168
|
+
// CHECK BEFORE ASSERTING, and never ship `--force` with a guess.
|
|
169
|
+
//
|
|
170
|
+
// "daemon may be gone" was stated unconditionally. On this machine it was
|
|
171
|
+
// false — the daemon had answered a heartbeat 112 ms earlier — because the
|
|
172
|
+
// target is pinned to `daemon_<hash>_launchd_default` while the live daemon
|
|
173
|
+
// is `daemon_<hash>_cli_default`: same machine, same directory, started a
|
|
174
|
+
// different way. So the CLI confidently recommended
|
|
175
|
+
// `remove, then purge-target --force` against live, correctly-installed
|
|
176
|
+
// managed blocks. An error that is confidently wrong AND carries its own
|
|
177
|
+
// `--force` is worse than one that says it does not know.
|
|
178
|
+
const elsewhere = sameMachineDaemon(target.daemonId || '', liveDaemonIds);
|
|
179
|
+
if (elsewhere) {
|
|
180
|
+
const live = parseDaemonId(elsewhere);
|
|
181
|
+
const pinned = parseDaemonId(target.daemonId || '');
|
|
182
|
+
// Kept under the STATUS column's `maxWidth: 78`, same constraint as below.
|
|
183
|
+
return `${base} — stalled ${days}d; daemon here now runs as ${live?.source}, not ${pinned?.source}: re-install`;
|
|
184
|
+
}
|
|
134
185
|
return `${base} — stalled ${days}d; daemon may be gone: remove, then purge-target --force`;
|
|
135
186
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* label-ref.ts — let a human type `--label dogfood` instead of a uuid.
|
|
3
|
+
*
|
|
4
|
+
* WHY
|
|
5
|
+
* ---
|
|
6
|
+
* Labels are addressed by id everywhere: `spaces labels add <space> --label
|
|
7
|
+
* <uuid>`. That is correct for the wire and hostile at a keyboard, and the cost
|
|
8
|
+
* was measurable — the fleet reached nine labels, every one of them
|
|
9
|
+
* `applicableTo: ['task']`, and not one space-applicable label had ever been
|
|
10
|
+
* created. Applying one meant `labels list`, eyeball the uuid, paste it. Three
|
|
11
|
+
* steps to do the thing the flag already existed for.
|
|
12
|
+
*
|
|
13
|
+
* THE RULE, AND WHY IT IS NOT "CREATE IT IF MISSING"
|
|
14
|
+
* -------------------------------------------------
|
|
15
|
+
* An unknown name is REFUSED, with the closest matches named. It is never
|
|
16
|
+
* created.
|
|
17
|
+
*
|
|
18
|
+
* Auto-create is the obvious convenience and it is the one thing that destroys
|
|
19
|
+
* the vocabulary: `--label dogfod` would mint a second label nobody meant, and
|
|
20
|
+
* a label is worth exactly as much as the completeness of a filter on it. The
|
|
21
|
+
* server draws the same line for every agent-supplied key — the operator
|
|
22
|
+
* declares the vocabulary, the caller chooses from it (`WakeIntentResolver`,
|
|
23
|
+
* the `agent_run` watched source, and `Labels/labelGuidance.js`). This is that
|
|
24
|
+
* rule at the keyboard instead of at the model.
|
|
25
|
+
*
|
|
26
|
+
* Refusing is also what makes the typo cheap: the caller sees
|
|
27
|
+
* `did you mean: dogfood?` and retries, instead of discovering six weeks later
|
|
28
|
+
* that half the program is under a name nobody can spell twice.
|
|
29
|
+
*
|
|
30
|
+
* An id passes through UNTOUCHED and costs no round trip — scripts and agents
|
|
31
|
+
* that already hold ids keep working exactly as before, and nothing here is on
|
|
32
|
+
* their path.
|
|
33
|
+
*/
|
|
34
|
+
import { dataService } from '@skrr-ai/data-provider';
|
|
35
|
+
import type { LabelApplicable } from './label-scope';
|
|
36
|
+
export declare function looksLikeLabelId(ref: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* A label row as the API returns it. Narrow on purpose at the fields this
|
|
39
|
+
* module reasons about; everything else rides along untouched so callers that
|
|
40
|
+
* RENDER labels (`labels list`) can use the same merge.
|
|
41
|
+
*/
|
|
42
|
+
export interface LabelRow {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
scope?: string;
|
|
46
|
+
scopeId?: string;
|
|
47
|
+
color?: string;
|
|
48
|
+
applicableTo?: string[];
|
|
49
|
+
usageCount?: number;
|
|
50
|
+
archivedAt?: string | null;
|
|
51
|
+
[key: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Names close enough to be worth printing. A suggestion list that includes
|
|
55
|
+
* everything is the same as no suggestion list — the caller still has to read
|
|
56
|
+
* all of it — so the threshold scales with the typed length and caps at three.
|
|
57
|
+
*/
|
|
58
|
+
export declare function closestNames(typed: string, names: string[], limit?: number): string[];
|
|
59
|
+
export interface LabelVocabularyContext {
|
|
60
|
+
/** Narrow to labels attachable to this entity type. Omit for all of them. */
|
|
61
|
+
applicableTo?: LabelApplicable;
|
|
62
|
+
/** Narrows to the workspace scope; `--workspace` / OVERSKY_WORKSPACE_ID. */
|
|
63
|
+
workspaceId?: string;
|
|
64
|
+
/** Archived labels are excluded unless asked for. */
|
|
65
|
+
includeArchived?: boolean;
|
|
66
|
+
/** Injected in tests. */
|
|
67
|
+
getLabels?: typeof dataService.getLabels;
|
|
68
|
+
}
|
|
69
|
+
export interface LabelRefContext extends LabelVocabularyContext {
|
|
70
|
+
/** Which entity the resolved labels will be attached to. */
|
|
71
|
+
applicableTo: LabelApplicable;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Every scope this caller could legitimately attach from, merged.
|
|
75
|
+
*
|
|
76
|
+
* `GET /api/labels` takes ONE scope, and a label the caller may attach can live
|
|
77
|
+
* in either of two. Asking for one and calling it "the vocabulary" would refuse
|
|
78
|
+
* a name that exists — which is the failure mode that makes people stop using
|
|
79
|
+
* names at all. A scope that cannot be read contributes nothing and throws
|
|
80
|
+
* nothing: this runs to produce a better error message, and must never become
|
|
81
|
+
* the error.
|
|
82
|
+
*/
|
|
83
|
+
export declare function collectLabelVocabulary(context: LabelVocabularyContext): Promise<LabelRow[]>;
|
|
84
|
+
export declare class LabelRefError extends Error {
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve `--label` / `--label-id` values, each either an id or a name.
|
|
88
|
+
*
|
|
89
|
+
* Throws `LabelRefError` with an actionable message when a name does not
|
|
90
|
+
* resolve, or resolves to more than one label. Ambiguity is a refusal rather
|
|
91
|
+
* than a pick: the same name may legitimately exist in a user scope and a
|
|
92
|
+
* workspace scope, and guessing which one the caller meant is how the wrong
|
|
93
|
+
* label ends up attached to a year of work.
|
|
94
|
+
*/
|
|
95
|
+
export declare function resolveLabelRefs(refs: string[] | undefined, context: LabelRefContext): Promise<string[]>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* label-ref.ts — let a human type `--label dogfood` instead of a uuid.
|
|
4
|
+
*
|
|
5
|
+
* WHY
|
|
6
|
+
* ---
|
|
7
|
+
* Labels are addressed by id everywhere: `spaces labels add <space> --label
|
|
8
|
+
* <uuid>`. That is correct for the wire and hostile at a keyboard, and the cost
|
|
9
|
+
* was measurable — the fleet reached nine labels, every one of them
|
|
10
|
+
* `applicableTo: ['task']`, and not one space-applicable label had ever been
|
|
11
|
+
* created. Applying one meant `labels list`, eyeball the uuid, paste it. Three
|
|
12
|
+
* steps to do the thing the flag already existed for.
|
|
13
|
+
*
|
|
14
|
+
* THE RULE, AND WHY IT IS NOT "CREATE IT IF MISSING"
|
|
15
|
+
* -------------------------------------------------
|
|
16
|
+
* An unknown name is REFUSED, with the closest matches named. It is never
|
|
17
|
+
* created.
|
|
18
|
+
*
|
|
19
|
+
* Auto-create is the obvious convenience and it is the one thing that destroys
|
|
20
|
+
* the vocabulary: `--label dogfod` would mint a second label nobody meant, and
|
|
21
|
+
* a label is worth exactly as much as the completeness of a filter on it. The
|
|
22
|
+
* server draws the same line for every agent-supplied key — the operator
|
|
23
|
+
* declares the vocabulary, the caller chooses from it (`WakeIntentResolver`,
|
|
24
|
+
* the `agent_run` watched source, and `Labels/labelGuidance.js`). This is that
|
|
25
|
+
* rule at the keyboard instead of at the model.
|
|
26
|
+
*
|
|
27
|
+
* Refusing is also what makes the typo cheap: the caller sees
|
|
28
|
+
* `did you mean: dogfood?` and retries, instead of discovering six weeks later
|
|
29
|
+
* that half the program is under a name nobody can spell twice.
|
|
30
|
+
*
|
|
31
|
+
* An id passes through UNTOUCHED and costs no round trip — scripts and agents
|
|
32
|
+
* that already hold ids keep working exactly as before, and nothing here is on
|
|
33
|
+
* their path.
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.LabelRefError = void 0;
|
|
37
|
+
exports.looksLikeLabelId = looksLikeLabelId;
|
|
38
|
+
exports.closestNames = closestNames;
|
|
39
|
+
exports.collectLabelVocabulary = collectLabelVocabulary;
|
|
40
|
+
exports.resolveLabelRefs = resolveLabelRefs;
|
|
41
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
42
|
+
/** A canonical v4 uuid, which is what every label id is. */
|
|
43
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
44
|
+
function looksLikeLabelId(ref) {
|
|
45
|
+
return UUID_RE.test(ref.trim());
|
|
46
|
+
}
|
|
47
|
+
/** Levenshtein, iterative, two rows. Only ever runs on a refusal path. */
|
|
48
|
+
function distance(a, b) {
|
|
49
|
+
if (a === b)
|
|
50
|
+
return 0;
|
|
51
|
+
if (!a.length)
|
|
52
|
+
return b.length;
|
|
53
|
+
if (!b.length)
|
|
54
|
+
return a.length;
|
|
55
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
56
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
57
|
+
const row = [i];
|
|
58
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
59
|
+
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
60
|
+
}
|
|
61
|
+
prev = row;
|
|
62
|
+
}
|
|
63
|
+
return prev[b.length];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Names close enough to be worth printing. A suggestion list that includes
|
|
67
|
+
* everything is the same as no suggestion list — the caller still has to read
|
|
68
|
+
* all of it — so the threshold scales with the typed length and caps at three.
|
|
69
|
+
*/
|
|
70
|
+
function closestNames(typed, names, limit = 3) {
|
|
71
|
+
const needle = typed.trim().toLowerCase();
|
|
72
|
+
const budget = Math.max(2, Math.ceil(needle.length / 3));
|
|
73
|
+
return names
|
|
74
|
+
.map((name) => ({ name, d: distance(needle, name.toLowerCase()) }))
|
|
75
|
+
.filter((row) => row.d <= budget || row.name.toLowerCase().includes(needle))
|
|
76
|
+
.sort((a, b) => a.d - b.d)
|
|
77
|
+
.slice(0, limit)
|
|
78
|
+
.map((row) => row.name);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Every scope this caller could legitimately attach from, merged.
|
|
82
|
+
*
|
|
83
|
+
* `GET /api/labels` takes ONE scope, and a label the caller may attach can live
|
|
84
|
+
* in either of two. Asking for one and calling it "the vocabulary" would refuse
|
|
85
|
+
* a name that exists — which is the failure mode that makes people stop using
|
|
86
|
+
* names at all. A scope that cannot be read contributes nothing and throws
|
|
87
|
+
* nothing: this runs to produce a better error message, and must never become
|
|
88
|
+
* the error.
|
|
89
|
+
*/
|
|
90
|
+
async function collectLabelVocabulary(context) {
|
|
91
|
+
const getLabels = context.getLabels ?? data_provider_1.dataService.getLabels;
|
|
92
|
+
const requests = [{ scope: 'user' }];
|
|
93
|
+
if (context.workspaceId)
|
|
94
|
+
requests.push({ scope: 'workspace', scopeId: context.workspaceId });
|
|
95
|
+
const settled = await Promise.allSettled(requests.map((request) => getLabels({
|
|
96
|
+
...request,
|
|
97
|
+
...(context.applicableTo ? { applicableTo: context.applicableTo } : {}),
|
|
98
|
+
...(context.includeArchived ? { includeArchived: true } : {}),
|
|
99
|
+
})));
|
|
100
|
+
const byId = new Map();
|
|
101
|
+
for (const outcome of settled) {
|
|
102
|
+
if (outcome.status !== 'fulfilled')
|
|
103
|
+
continue;
|
|
104
|
+
for (const row of outcome.value?.data ?? []) {
|
|
105
|
+
if (row?.id && !byId.has(row.id))
|
|
106
|
+
byId.set(row.id, row);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return [...byId.values()];
|
|
110
|
+
}
|
|
111
|
+
class LabelRefError extends Error {
|
|
112
|
+
}
|
|
113
|
+
exports.LabelRefError = LabelRefError;
|
|
114
|
+
/**
|
|
115
|
+
* Resolve `--label` / `--label-id` values, each either an id or a name.
|
|
116
|
+
*
|
|
117
|
+
* Throws `LabelRefError` with an actionable message when a name does not
|
|
118
|
+
* resolve, or resolves to more than one label. Ambiguity is a refusal rather
|
|
119
|
+
* than a pick: the same name may legitimately exist in a user scope and a
|
|
120
|
+
* workspace scope, and guessing which one the caller meant is how the wrong
|
|
121
|
+
* label ends up attached to a year of work.
|
|
122
|
+
*/
|
|
123
|
+
async function resolveLabelRefs(refs, context) {
|
|
124
|
+
const input = (refs ?? []).map((ref) => ref.trim()).filter(Boolean);
|
|
125
|
+
if (input.length === 0)
|
|
126
|
+
return [];
|
|
127
|
+
// Ids are the wire form and the common case for scripts. Resolve nothing,
|
|
128
|
+
// fetch nothing, and let the server be the authority on whether they exist.
|
|
129
|
+
if (input.every(looksLikeLabelId))
|
|
130
|
+
return input;
|
|
131
|
+
const vocabulary = await collectLabelVocabulary(context);
|
|
132
|
+
const resolved = [];
|
|
133
|
+
for (const ref of input) {
|
|
134
|
+
if (looksLikeLabelId(ref)) {
|
|
135
|
+
resolved.push(ref);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const needle = ref.toLowerCase();
|
|
139
|
+
const matches = vocabulary.filter((row) => (row.name ?? '').toLowerCase() === needle);
|
|
140
|
+
if (matches.length === 1) {
|
|
141
|
+
resolved.push(matches[0].id);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (matches.length > 1) {
|
|
145
|
+
const listed = matches
|
|
146
|
+
.map((row) => ` ${row.id} ${row.name}${row.scope ? ` (${row.scope})` : ''}`)
|
|
147
|
+
.join('\n');
|
|
148
|
+
throw new LabelRefError(`"${ref}" names ${matches.length} labels. Pass the id you mean:\n${listed}`);
|
|
149
|
+
}
|
|
150
|
+
if (vocabulary.length === 0) {
|
|
151
|
+
throw new LabelRefError(`No label named "${ref}" — and no label applies to ${context.applicableTo}s yet. ` +
|
|
152
|
+
`Create one with \`labels create --name ${ref} --applicable-to ${context.applicableTo}\`.`);
|
|
153
|
+
}
|
|
154
|
+
const near = closestNames(ref, vocabulary.map((row) => row.name));
|
|
155
|
+
throw new LabelRefError(`No label named "${ref}" applies to ${context.applicableTo}s.` +
|
|
156
|
+
(near.length ? ` Did you mean: ${near.join(', ')}?` : '') +
|
|
157
|
+
` Run \`labels list\` for the vocabulary, or create it deliberately with ` +
|
|
158
|
+
`\`labels create --name ${ref} --applicable-to ${context.applicableTo}\` — ` +
|
|
159
|
+
'a name is never created implicitly, because a second spelling of a label ' +
|
|
160
|
+
'is worse than no label.');
|
|
161
|
+
}
|
|
162
|
+
return resolved;
|
|
163
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared plumbing for the unified label commands (`skrr labels …`).
|
|
3
3
|
*
|
|
4
|
-
* Labels are anchored to exactly one scope — a user
|
|
4
|
+
* Labels are anchored to exactly one scope — a user or a workspace —
|
|
5
5
|
* via the `(scope, scopeId)` pair. That pair is simultaneously the uniqueness
|
|
6
6
|
* key and the permission boundary server-side (see
|
|
7
7
|
* `api/server/services/Labels/LabelService.js`), so every label command has to
|
|
@@ -9,8 +9,23 @@
|
|
|
9
9
|
* always explicit; `user` is the default because that is the only scope whose
|
|
10
10
|
* id the server can infer (it falls back to the caller).
|
|
11
11
|
*/
|
|
12
|
-
export declare const LABEL_SCOPES: readonly ["user", "
|
|
12
|
+
export declare const LABEL_SCOPES: readonly ["user", "workspace"];
|
|
13
13
|
export type LabelScope = (typeof LABEL_SCOPES)[number];
|
|
14
|
+
/**
|
|
15
|
+
* Scopes whose id the server cannot infer, so `--scope-id` must name it.
|
|
16
|
+
*
|
|
17
|
+
* `resolveLabelScope` below and the `--scope-id` help text are BOTH derived
|
|
18
|
+
* from this, rather than each restating the list. They drifted once already:
|
|
19
|
+
* `0298b7e4fd` collapsed the vocabulary from three scopes to two, and the help
|
|
20
|
+
* kept advertising `--scope space` for months after the validator had stopped
|
|
21
|
+
* accepting it — so following `--help` produced `Expected --scope=space to be
|
|
22
|
+
* one of: user, workspace`, whose last line points back at the help that was
|
|
23
|
+
* wrong. Static prose describing a value set is how that happens; deriving it
|
|
24
|
+
* is what stops the next collapse doing the same.
|
|
25
|
+
*/
|
|
26
|
+
export declare const LABEL_SCOPES_REQUIRING_ID: readonly ["workspace"];
|
|
27
|
+
/** Scopes the server resolves from the caller — passing an id is an error. */
|
|
28
|
+
export declare const LABEL_SCOPES_INFERRED: "user"[];
|
|
14
29
|
export declare const LABEL_APPLICABLE: readonly ["task", "space", "goal", "routine"];
|
|
15
30
|
export type LabelApplicable = (typeof LABEL_APPLICABLE)[number];
|
|
16
31
|
/**
|