@synap-core/cli 1.12.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -8
- package/dist/build-info.json +2 -2
- package/dist/commands/agents.d.ts +0 -1
- package/dist/commands/agents.js +1 -1
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/app.js +2 -1
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/automation.js +46 -41
- package/dist/commands/automation.js.map +1 -1
- package/dist/commands/box.d.ts +42 -0
- package/dist/commands/box.js +89 -0
- package/dist/commands/box.js.map +1 -0
- package/dist/commands/bridge-setup.js +3 -1
- package/dist/commands/bridge-setup.js.map +1 -1
- package/dist/commands/capability.d.ts +1 -1
- package/dist/commands/capability.js +57 -34
- package/dist/commands/capability.js.map +1 -1
- package/dist/commands/close-questions.d.ts +27 -0
- package/dist/commands/close-questions.js +49 -0
- package/dist/commands/close-questions.js.map +1 -0
- package/dist/commands/close-questions.test.d.ts +1 -0
- package/dist/commands/close-questions.test.js +52 -0
- package/dist/commands/close-questions.test.js.map +1 -0
- package/dist/commands/connect.d.ts +1 -3
- package/dist/commands/connect.js +2 -2
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/context.js +72 -43
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/data-extra.js +12 -4
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/data.js +2 -1
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/doctor.d.ts +21 -0
- package/dist/commands/doctor.js +108 -13
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +70 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/import.d.ts +25 -0
- package/dist/commands/import.js +98 -12
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.js +59 -19
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/knowledge.js +4 -2
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/lens.js +2 -1
- package/dist/commands/lens.js.map +1 -1
- package/dist/commands/market-authoring.js +1 -1
- package/dist/commands/market-authoring.js.map +1 -1
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/observe.category.test.d.ts +1 -0
- package/dist/commands/observe.category.test.js +59 -0
- package/dist/commands/observe.category.test.js.map +1 -0
- package/dist/commands/observe.d.ts +3 -0
- package/dist/commands/observe.js +7 -5
- package/dist/commands/observe.js.map +1 -1
- package/dist/commands/observe.title.test.d.ts +1 -0
- package/dist/commands/observe.title.test.js +37 -0
- package/dist/commands/observe.title.test.js.map +1 -0
- package/dist/commands/playbook.js +9 -0
- package/dist/commands/playbook.js.map +1 -1
- package/dist/commands/pods.d.ts +6 -1
- package/dist/commands/pods.js +32 -15
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proton-setup.js +3 -1
- package/dist/commands/proton-setup.js.map +1 -1
- package/dist/commands/session-feed-wait.d.ts +48 -0
- package/dist/commands/session-feed-wait.js +261 -0
- package/dist/commands/session-feed-wait.js.map +1 -0
- package/dist/commands/session-post.d.ts +40 -0
- package/dist/commands/session-post.js +96 -0
- package/dist/commands/session-post.js.map +1 -0
- package/dist/commands/session-wait.d.ts +9 -4
- package/dist/commands/session-wait.js +20 -5
- package/dist/commands/session-wait.js.map +1 -1
- package/dist/commands/sessions.d.ts +2 -0
- package/dist/commands/sessions.js +23 -0
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/skills-commands.d.ts +2 -0
- package/dist/commands/skills-commands.js +9 -1
- package/dist/commands/skills-commands.js.map +1 -1
- package/dist/commands/skills-commands.test.d.ts +1 -0
- package/dist/commands/skills-commands.test.js +57 -0
- package/dist/commands/skills-commands.test.js.map +1 -0
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/template.d.ts +42 -0
- package/dist/commands/template.js +191 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/tracks.d.ts +67 -6
- package/dist/commands/tracks.js +268 -16
- package/dist/commands/tracks.js.map +1 -1
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/worker-accounts.d.ts +149 -0
- package/dist/commands/worker-accounts.js +339 -0
- package/dist/commands/worker-accounts.js.map +1 -0
- package/dist/commands/worker-accounts.test.d.ts +1 -0
- package/dist/commands/worker-accounts.test.js +224 -0
- package/dist/commands/worker-accounts.test.js.map +1 -0
- package/dist/commands/worker.d.ts +219 -10
- package/dist/commands/worker.js +749 -117
- package/dist/commands/worker.js.map +1 -1
- package/dist/commands/worker.test.js +321 -10
- package/dist/commands/worker.test.js.map +1 -1
- package/dist/index.js +164 -27
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-door.d.ts +1 -3
- package/dist/lib/agent-door.js +10 -7
- package/dist/lib/agent-door.js.map +1 -1
- package/dist/lib/auth.d.ts +3 -2
- package/dist/lib/auth.js +3 -3
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/box/egress.d.ts +82 -0
- package/dist/lib/box/egress.js +264 -0
- package/dist/lib/box/egress.js.map +1 -0
- package/dist/lib/box/evidence.d.ts +86 -0
- package/dist/lib/box/evidence.js +121 -0
- package/dist/lib/box/evidence.js.map +1 -0
- package/dist/lib/box/job-server.d.ts +72 -0
- package/dist/lib/box/job-server.js +246 -0
- package/dist/lib/box/job-server.js.map +1 -0
- package/dist/lib/box/provisioner.d.ts +83 -0
- package/dist/lib/box/provisioner.js +312 -0
- package/dist/lib/box/provisioner.js.map +1 -0
- package/dist/lib/box/proxmox.d.ts +112 -0
- package/dist/lib/box/proxmox.js +211 -0
- package/dist/lib/box/proxmox.js.map +1 -0
- package/dist/lib/box/wire.d.ts +98 -0
- package/dist/lib/box/wire.js +70 -0
- package/dist/lib/box/wire.js.map +1 -0
- package/dist/lib/browser-auth.d.ts +35 -0
- package/dist/lib/browser-auth.js +170 -0
- package/dist/lib/browser-auth.js.map +1 -0
- package/dist/lib/client-header.test.d.ts +1 -0
- package/dist/lib/client-header.test.js +85 -0
- package/dist/lib/client-header.test.js.map +1 -0
- package/dist/lib/conversation-headers.d.ts +24 -9
- package/dist/lib/conversation-headers.js +40 -3
- package/dist/lib/conversation-headers.js.map +1 -1
- package/dist/lib/credential-class.d.ts +28 -1
- package/dist/lib/credential-class.js +28 -6
- package/dist/lib/credential-class.js.map +1 -1
- package/dist/lib/exporter-coverage.d.ts +1 -1
- package/dist/lib/exporter-coverage.js +1 -1
- package/dist/lib/goal-words.d.ts +38 -0
- package/dist/lib/goal-words.js +112 -0
- package/dist/lib/goal-words.js.map +1 -0
- package/dist/lib/hub-client.d.ts +28 -7
- package/dist/lib/hub-client.js +75 -34
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/init-handoff.d.ts +8 -0
- package/dist/lib/init-handoff.js +9 -2
- package/dist/lib/init-handoff.js.map +1 -1
- package/dist/lib/key-source.d.ts +3 -1
- package/dist/lib/key-source.js +7 -4
- package/dist/lib/key-source.js.map +1 -1
- package/dist/lib/knowledge-title.d.ts +3 -2
- package/dist/lib/knowledge-title.js +3 -2
- package/dist/lib/knowledge-title.js.map +1 -1
- package/dist/lib/mcp-bridge.d.ts +7 -0
- package/dist/lib/mcp-bridge.js +34 -1
- package/dist/lib/mcp-bridge.js.map +1 -1
- package/dist/lib/mcp-bridge.test.js +31 -3
- package/dist/lib/mcp-bridge.test.js.map +1 -1
- package/dist/lib/openclaw.d.ts +32 -0
- package/dist/lib/openclaw.js +165 -0
- package/dist/lib/openclaw.js.map +1 -0
- package/dist/lib/pod-moved.d.ts +70 -0
- package/dist/lib/pod-moved.js +146 -0
- package/dist/lib/pod-moved.js.map +1 -0
- package/dist/lib/pod-reach.d.ts +39 -0
- package/dist/lib/pod-reach.js +117 -0
- package/dist/lib/pod-reach.js.map +1 -0
- package/dist/lib/pod-reach.test.d.ts +1 -0
- package/dist/lib/pod-reach.test.js +67 -0
- package/dist/lib/pod-reach.test.js.map +1 -0
- package/dist/lib/pod.d.ts +8 -0
- package/dist/lib/pod.js +12 -3
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/register-claude-code-mcp.test.d.ts +1 -0
- package/dist/lib/register-claude-code-mcp.test.js +78 -0
- package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
- package/dist/lib/review-credential.js +3 -1
- package/dist/lib/review-credential.js.map +1 -1
- package/dist/lib/seed.d.ts +13 -0
- package/dist/lib/seed.js +135 -0
- package/dist/lib/seed.js.map +1 -0
- package/dist/lib/session-feed.d.ts +185 -0
- package/dist/lib/session-feed.js +257 -0
- package/dist/lib/session-feed.js.map +1 -0
- package/dist/lib/targets.d.ts +21 -3
- package/dist/lib/targets.js +52 -107
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/wake-hook-settings.d.ts +57 -0
- package/dist/lib/wake-hook-settings.js +153 -0
- package/dist/lib/wake-hook-settings.js.map +1 -0
- package/dist/lib/wake-state.d.ts +65 -0
- package/dist/lib/wake-state.js +122 -0
- package/dist/lib/wake-state.js.map +1 -0
- package/package.json +5 -2
- package/skills/README.md +1 -1
- package/skills/agent-os/SKILL.md +8 -8
- package/skills/synap/SKILL.md +141 -84
- package/skills/synap/code-skills.md +70 -0
- package/skills/synap/concepts.md +28 -20
- package/skills/synap/creative-loop.md +1 -1
- package/skills/synap/document-embeds.md +3 -1
- package/skills/synap/focus-sessions.md +16 -7
- package/skills/synap/from-intent.md +32 -16
- package/skills/synap/governance-rules.md +1 -1
- package/skills/synap/lenses.md +4 -4
- package/skills/synap/linking.md +2 -2
- package/skills/synap/mental-model.md +1 -1
- package/skills/synap/operating-mode-rules.md +1 -1
- package/skills/synap/quick-reference.md +2 -1
- package/skills/synap/reflexes.md +6 -6
- package/skills/synap/viewframe-cells.md +1 -1
- package/skills/synap/work-model.md +24 -10
- package/skills/synap/workspace-design.md +5 -5
- package/skills/synap/writes.md +16 -9
- package/skills/synap-app/SKILL.md +148 -66
- package/skills/synap-app/creating-the-base-app.md +12 -7
- package/skills/synap-app/governance.md +27 -16
- package/skills/synap-app/install-commands.md +18 -9
- package/skills/synap-app/overview.md +16 -9
- package/skills/synap-app/using-the-sdk.md +75 -25
- package/skills/synap-schema/SKILL.md +21 -21
- package/skills/synap-schema/intent-vocabulary-ssot.md +21 -21
- package/templates/box/runner.py +206 -0
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* blocked), classification is deliberately three-valued and the guard is
|
|
29
29
|
* belt-and-braces:
|
|
30
30
|
* • `agent` — positive agent signal → refuse locally, before the network call.
|
|
31
|
-
* • `human` — positive human signal (`user_pat`) → never blocked.
|
|
31
|
+
* • `human` — positive human signal (`user_pat` NOT owned by an agent user) → never blocked.
|
|
32
32
|
* • `unknown` — no signal → PROCEED, and translate the server's 403 after the
|
|
33
33
|
* fact (`isAgentReviewRejection`). The server stays authoritative.
|
|
34
34
|
*
|
|
@@ -58,9 +58,36 @@ export interface AuthStatus {
|
|
|
58
58
|
isActive?: boolean;
|
|
59
59
|
keyType?: string | null;
|
|
60
60
|
workspaceId?: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* What the key may touch — its grant (pod ≥ owner-app-connect-key; absent
|
|
63
|
+
* on older pods, null when the pod read no grant for the request).
|
|
64
|
+
*/
|
|
65
|
+
access?: KeyAccess | null;
|
|
66
|
+
}
|
|
67
|
+
/** `/auth/status` `access`: the calling key's grant, ids and patterns only. */
|
|
68
|
+
export interface KeyAccess {
|
|
69
|
+
isFullAccess: boolean;
|
|
70
|
+
/** "full access", or what limits it ("limited to 1 space (<id>)"). */
|
|
71
|
+
summary: string;
|
|
72
|
+
scopes: Array<{
|
|
73
|
+
permissions: string[];
|
|
74
|
+
workspaceIds: string[] | null;
|
|
75
|
+
projectIds: string[] | null;
|
|
76
|
+
entityIds: string[] | null;
|
|
77
|
+
}>;
|
|
78
|
+
clientId: string | null;
|
|
61
79
|
}
|
|
62
80
|
/** Introspect a key. `apiKey` overrides `cfg.apiKey` (used by `whoami` to probe both sides of a divergence). */
|
|
63
81
|
export declare function fetchAuthStatus(cfg: HubConfig, apiKey?: string): Promise<AuthStatus>;
|
|
82
|
+
/**
|
|
83
|
+
* WHO this key is — the agent principal, for recognising the agent's OWN
|
|
84
|
+
* posts (a room message carries it as `routedTeammateId`). Never the acting
|
|
85
|
+
* user sent to the pod: that is `resolveUserId` (/users/me), which for an
|
|
86
|
+
* agent key answers the linked HUMAN. The id recorded with the key
|
|
87
|
+
* (`cfg.agentUserId`) wins; else the pod's key-owner answer (/auth/status),
|
|
88
|
+
* cached per key. For a person's own key both questions answer the person.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveAgentUserId(cfg: HubConfig): Promise<string>;
|
|
64
91
|
/**
|
|
65
92
|
* Shape of `GET /api/hub/users/me` (`hub-protocol/rest/users.ts:36-46`).
|
|
66
93
|
*
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* blocked), classification is deliberately three-valued and the guard is
|
|
29
29
|
* belt-and-braces:
|
|
30
30
|
* • `agent` — positive agent signal → refuse locally, before the network call.
|
|
31
|
-
* • `human` — positive human signal (`user_pat`) → never blocked.
|
|
31
|
+
* • `human` — positive human signal (`user_pat` NOT owned by an agent user) → never blocked.
|
|
32
32
|
* • `unknown` — no signal → PROCEED, and translate the server's 403 after the
|
|
33
33
|
* fact (`isAgentReviewRejection`). The server stays authoritative.
|
|
34
34
|
*
|
|
@@ -47,6 +47,27 @@ import { hubGet, HubError } from "./hub-client.js";
|
|
|
47
47
|
export async function fetchAuthStatus(cfg, apiKey) {
|
|
48
48
|
return (await hubGet("/auth/status", {}, apiKey ? { ...cfg, apiKey } : cfg));
|
|
49
49
|
}
|
|
50
|
+
const agentUserIdCache = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* WHO this key is — the agent principal, for recognising the agent's OWN
|
|
53
|
+
* posts (a room message carries it as `routedTeammateId`). Never the acting
|
|
54
|
+
* user sent to the pod: that is `resolveUserId` (/users/me), which for an
|
|
55
|
+
* agent key answers the linked HUMAN. The id recorded with the key
|
|
56
|
+
* (`cfg.agentUserId`) wins; else the pod's key-owner answer (/auth/status),
|
|
57
|
+
* cached per key. For a person's own key both questions answer the person.
|
|
58
|
+
*/
|
|
59
|
+
export async function resolveAgentUserId(cfg) {
|
|
60
|
+
if (cfg.agentUserId)
|
|
61
|
+
return cfg.agentUserId;
|
|
62
|
+
const cached = agentUserIdCache.get(cfg.apiKey);
|
|
63
|
+
if (cached)
|
|
64
|
+
return cached;
|
|
65
|
+
const owner = (await fetchAuthStatus(cfg)).userId;
|
|
66
|
+
if (!owner)
|
|
67
|
+
throw new Error("The pod did not say who owns this key (GET /auth/status has no userId).");
|
|
68
|
+
agentUserIdCache.set(cfg.apiKey, owner);
|
|
69
|
+
return owner;
|
|
70
|
+
}
|
|
50
71
|
/** Resolve the effective identity. `apiKey` overrides `cfg.apiKey`, mirroring `fetchAuthStatus`. */
|
|
51
72
|
export async function fetchEffectiveIdentity(cfg, apiKey) {
|
|
52
73
|
return (await hubGet("/users/me", {}, apiKey ? { ...cfg, apiKey } : cfg));
|
|
@@ -56,14 +77,15 @@ const AGENT_EMAIL_DOMAIN = "@synap.agent";
|
|
|
56
77
|
export function classifyReviewer(status) {
|
|
57
78
|
if (!status)
|
|
58
79
|
return "unknown";
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
return "human";
|
|
80
|
+
// The agent domain FIRST: the server derives the agent from the key owner's
|
|
81
|
+
// `userType === 'agent'` (access/key-identity.ts), whatever the key type, and
|
|
82
|
+
// only agent users carry the synthetic domain. The device flow mints
|
|
83
|
+
// `user_pat` keys owned by a machine agent, so keyType alone is not human.
|
|
64
84
|
const email = (status.userEmail ?? "").toLowerCase();
|
|
65
85
|
if (email.endsWith(AGENT_EMAIL_DOMAIN))
|
|
66
86
|
return "agent";
|
|
87
|
+
if (status.keyType === "user_pat")
|
|
88
|
+
return "human";
|
|
67
89
|
return "unknown";
|
|
68
90
|
}
|
|
69
91
|
/** Best-effort classification. A pod that is down/older must not block the command — it degrades to `unknown`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-class.js","sourceRoot":"","sources":["../../src/lib/credential-class.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"credential-class.js","sourceRoot":"","sources":["../../src/lib/credential-class.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Loops stay here deliberately: there is no durable loop row to reverse-
|
|
16
16
|
* serialise (`createLoopFromDefinition` dissolves into playbooks + triggers).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* The template guide used to assert, unqualified, that a
|
|
19
19
|
* `PackageDefinition` "round-trips". It does not, in this direction, and the
|
|
20
20
|
* consequence is the shape this repo keeps paying for: an author who built one
|
|
21
21
|
* of the things below publishes their workspace, ships a thinner-than-expected
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Loops stay here deliberately: there is no durable loop row to reverse-
|
|
16
16
|
* serialise (`createLoopFromDefinition` dissolves into playbooks + triggers).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* The template guide used to assert, unqualified, that a
|
|
19
19
|
* `PackageDefinition` "round-trips". It does not, in this direction, and the
|
|
20
20
|
* consequence is the shape this repo keeps paying for: an author who built one
|
|
21
21
|
* of the things below publishes their workspace, ships a thinner-than-expected
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A GOAL's words as `synap track` prints them — status, verdict, kind of
|
|
3
|
+
* work, uncertainty, criterion marks, what it waits on, who is on it, the
|
|
4
|
+
* end state's label.
|
|
5
|
+
*
|
|
6
|
+
* `@synap-core/types/vocabulary` is the one door for these words, but this
|
|
7
|
+
* package cannot import it (the CLI links only `@synap-core/hub-rest-client`
|
|
8
|
+
* and `@synap-core/workspace-templates`). So each table here is a COPY of the
|
|
9
|
+
* vocabulary's own, held EQUAL — keys and words — by
|
|
10
|
+
* `test/goal-words-parity.test.ts`, which reads the vocabulary's source in
|
|
11
|
+
* the sibling `synap-backend`. Delete this file for a plain import once the
|
|
12
|
+
* CLI depends on a published `@synap-core/types` carrying them.
|
|
13
|
+
*/
|
|
14
|
+
/** `TRACK_STATUS_LABELS` */
|
|
15
|
+
export declare const TRACK_STATUS_LABELS: Readonly<Record<string, string>>;
|
|
16
|
+
/** `TRACK_VERDICT_LABELS` */
|
|
17
|
+
export declare const TRACK_VERDICT_LABELS: Readonly<Record<string, string>>;
|
|
18
|
+
/** `TRACK_WORK_KIND_LABELS` */
|
|
19
|
+
export declare const TRACK_WORK_KIND_LABELS: Readonly<Record<string, string>>;
|
|
20
|
+
/** `TRACK_UNCERTAINTY_LABELS` */
|
|
21
|
+
export declare const TRACK_UNCERTAINTY_LABELS: Readonly<Record<string, string>>;
|
|
22
|
+
/** `GOAL_CRITERION_MARK_LABELS` */
|
|
23
|
+
export declare const GOAL_CRITERION_MARK_LABELS: Readonly<Record<string, string>>;
|
|
24
|
+
/** `WAITING_ON_REASON_LABELS` */
|
|
25
|
+
export declare const WAITING_ON_REASON_LABELS: Readonly<Record<string, string>>;
|
|
26
|
+
/** `TRACK_PARTICIPANT_ROLE_LABELS` (vocabulary/track-participant-words.ts) */
|
|
27
|
+
export declare const TRACK_PARTICIPANT_ROLE_LABELS: Readonly<Record<string, string>>;
|
|
28
|
+
/** `TRACK_COMMITMENT_STATE_LABELS` (vocabulary/track-participant-words.ts) */
|
|
29
|
+
export declare const TRACK_COMMITMENT_STATE_LABELS: Readonly<Record<string, string>>;
|
|
30
|
+
/** `GOAL_FIELD_LABELS` — the goal's stored fields as a person reads them. */
|
|
31
|
+
export declare const GOAL_FIELD_LABELS: Readonly<Record<string, string>>;
|
|
32
|
+
/**
|
|
33
|
+
* The vocabulary's fallback (`humanizeToken`, minus its cased-words list): a
|
|
34
|
+
* value no table names reads as words, never as the raw token.
|
|
35
|
+
*/
|
|
36
|
+
export declare function humanizeToken(token: string): string;
|
|
37
|
+
/** A token through its table, else humanized — `""` for none. */
|
|
38
|
+
export declare function goalWord(table: Readonly<Record<string, string>>, token: string | null | undefined): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A GOAL's words as `synap track` prints them — status, verdict, kind of
|
|
3
|
+
* work, uncertainty, criterion marks, what it waits on, who is on it, the
|
|
4
|
+
* end state's label.
|
|
5
|
+
*
|
|
6
|
+
* `@synap-core/types/vocabulary` is the one door for these words, but this
|
|
7
|
+
* package cannot import it (the CLI links only `@synap-core/hub-rest-client`
|
|
8
|
+
* and `@synap-core/workspace-templates`). So each table here is a COPY of the
|
|
9
|
+
* vocabulary's own, held EQUAL — keys and words — by
|
|
10
|
+
* `test/goal-words-parity.test.ts`, which reads the vocabulary's source in
|
|
11
|
+
* the sibling `synap-backend`. Delete this file for a plain import once the
|
|
12
|
+
* CLI depends on a published `@synap-core/types` carrying them.
|
|
13
|
+
*/
|
|
14
|
+
/** `TRACK_STATUS_LABELS` */
|
|
15
|
+
export const TRACK_STATUS_LABELS = {
|
|
16
|
+
proposed: "Proposed",
|
|
17
|
+
agreed: "Agreed",
|
|
18
|
+
ready: "Ready",
|
|
19
|
+
active: "Active",
|
|
20
|
+
paused: "Paused",
|
|
21
|
+
completed: "Closed",
|
|
22
|
+
archived: "Dropped",
|
|
23
|
+
};
|
|
24
|
+
/** `TRACK_VERDICT_LABELS` */
|
|
25
|
+
export const TRACK_VERDICT_LABELS = {
|
|
26
|
+
met: "Met",
|
|
27
|
+
partly_met: "Partly met",
|
|
28
|
+
not_met: "Not met",
|
|
29
|
+
retired: "Retired",
|
|
30
|
+
no_longer_needed: "No longer needed",
|
|
31
|
+
impossible: "Impossible",
|
|
32
|
+
replaced: "Replaced",
|
|
33
|
+
mistake: "Created by mistake",
|
|
34
|
+
};
|
|
35
|
+
/** `TRACK_WORK_KIND_LABELS` */
|
|
36
|
+
export const TRACK_WORK_KIND_LABELS = {
|
|
37
|
+
project: "Project",
|
|
38
|
+
flow: "Flow",
|
|
39
|
+
operation: "Operation",
|
|
40
|
+
investigation: "Investigation",
|
|
41
|
+
incident: "Incident",
|
|
42
|
+
commission: "Commission",
|
|
43
|
+
pathway: "Pathway",
|
|
44
|
+
};
|
|
45
|
+
/** `TRACK_UNCERTAINTY_LABELS` */
|
|
46
|
+
export const TRACK_UNCERTAINTY_LABELS = {
|
|
47
|
+
clear: "Clear",
|
|
48
|
+
complicated: "Complicated",
|
|
49
|
+
complex: "Complex",
|
|
50
|
+
chaotic: "Chaotic",
|
|
51
|
+
unknown: "Unknown",
|
|
52
|
+
};
|
|
53
|
+
/** `GOAL_CRITERION_MARK_LABELS` */
|
|
54
|
+
export const GOAL_CRITERION_MARK_LABELS = {
|
|
55
|
+
met: "Met",
|
|
56
|
+
claimed: "Claimed",
|
|
57
|
+
under_way: "Under way",
|
|
58
|
+
not_met: "Not met",
|
|
59
|
+
not_started: "Not started",
|
|
60
|
+
};
|
|
61
|
+
/** `WAITING_ON_REASON_LABELS` */
|
|
62
|
+
export const WAITING_ON_REASON_LABELS = {
|
|
63
|
+
input: "Input",
|
|
64
|
+
approval: "Approval",
|
|
65
|
+
access: "Access",
|
|
66
|
+
another_work: "Other work",
|
|
67
|
+
outside_world: "Outside world",
|
|
68
|
+
};
|
|
69
|
+
/** `TRACK_PARTICIPANT_ROLE_LABELS` (vocabulary/track-participant-words.ts) */
|
|
70
|
+
export const TRACK_PARTICIPANT_ROLE_LABELS = {
|
|
71
|
+
owner: "Owner",
|
|
72
|
+
judge: "Judge",
|
|
73
|
+
delegate: "Delegate",
|
|
74
|
+
integrator: "Integrator",
|
|
75
|
+
};
|
|
76
|
+
/** `TRACK_COMMITMENT_STATE_LABELS` (vocabulary/track-participant-words.ts) */
|
|
77
|
+
export const TRACK_COMMITMENT_STATE_LABELS = {
|
|
78
|
+
proposed: "Asked",
|
|
79
|
+
accepted: "Accepted",
|
|
80
|
+
countered: "Countered",
|
|
81
|
+
declined: "Declined",
|
|
82
|
+
reported_impossible: "Reported impossible",
|
|
83
|
+
done: "Done",
|
|
84
|
+
};
|
|
85
|
+
/** `GOAL_FIELD_LABELS` — the goal's stored fields as a person reads them. */
|
|
86
|
+
export const GOAL_FIELD_LABELS = {
|
|
87
|
+
endState: "What done looks like",
|
|
88
|
+
criteria: "Done when",
|
|
89
|
+
workKind: "Kind of work",
|
|
90
|
+
uncertainty: "How well the way is known",
|
|
91
|
+
dropConditions: "Drop it if",
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* The vocabulary's fallback (`humanizeToken`, minus its cased-words list): a
|
|
95
|
+
* value no table names reads as words, never as the raw token.
|
|
96
|
+
*/
|
|
97
|
+
export function humanizeToken(token) {
|
|
98
|
+
const tail = token.includes(".") ? token.slice(token.lastIndexOf(".") + 1) : token;
|
|
99
|
+
const words = tail
|
|
100
|
+
.replace(/[_-]+/g, " ")
|
|
101
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
102
|
+
.trim()
|
|
103
|
+
.toLowerCase();
|
|
104
|
+
return words ? words.charAt(0).toUpperCase() + words.slice(1) : "";
|
|
105
|
+
}
|
|
106
|
+
/** A token through its table, else humanized — `""` for none. */
|
|
107
|
+
export function goalWord(table, token) {
|
|
108
|
+
if (!token)
|
|
109
|
+
return "";
|
|
110
|
+
return Object.hasOwn(table, token) ? table[token] : humanizeToken(token);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=goal-words.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-words.js","sourceRoot":"","sources":["../../src/lib/goal-words.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/hub-client.d.ts
CHANGED
|
@@ -60,23 +60,44 @@ export declare class HubNetworkError extends Error {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* terminal, a plain URL when the stream is redirected/piped.
|
|
63
|
+
* Where to send users who hit a wall. The support door is the ONE customer
|
|
64
|
+
* support entry point shared by every Synap surface (`from=cli` tells support
|
|
65
|
+
* which surface the person came from); Discord stays as the community option.
|
|
67
66
|
*/
|
|
68
|
-
export declare
|
|
67
|
+
export declare const SUPPORT_URL = "https://synap.live/support?from=cli";
|
|
68
|
+
/**
|
|
69
|
+
* Append a one-line "need help?" footer naming the support door (open a ticket)
|
|
70
|
+
* and the Discord invite. Prints to stderr (same stream as the error it
|
|
71
|
+
* follows, so `2>/dev/null` hides both) and only once per process. Each URL is
|
|
72
|
+
* an OSC-8 hyperlink on an interactive terminal, a plain URL when the stream is
|
|
73
|
+
* redirected/piped.
|
|
74
|
+
*/
|
|
75
|
+
export declare function helpFooterLine(): void;
|
|
69
76
|
/**
|
|
70
77
|
* Print a failed Hub call: its humanized message plus a hint for the classes
|
|
71
78
|
* where the cause is not obvious from the message alone. Lives here rather than
|
|
72
79
|
* in utils/logger so the logger stays domain-agnostic and the taxonomy and its
|
|
73
|
-
* presentation sit in one file. Always closes with the one-time
|
|
80
|
+
* presentation sit in one file. Always closes with the one-time help footer.
|
|
74
81
|
*/
|
|
75
82
|
export declare function renderHubError(err: unknown): void;
|
|
76
83
|
export interface HubConfig {
|
|
77
84
|
podUrl: string;
|
|
78
85
|
apiKey: string;
|
|
86
|
+
/**
|
|
87
|
+
* The ACTING identity sent to the pod (body/query/path `userId`). Only an
|
|
88
|
+
* explicit caller id (SYNAP_USER_ID) or the sentinel "cli"; read it through
|
|
89
|
+
* `resolveUserId`, which asks the pod (/users/me) for "cli". Never seed it
|
|
90
|
+
* with a recorded agent id: the pod acts an agent key as the human it is
|
|
91
|
+
* linked to and 403s the agent's own id ("userId does not match the
|
|
92
|
+
* authenticated identity", 2026-10-23). The agent's id is `agentUserId`.
|
|
93
|
+
*/
|
|
79
94
|
userId: string;
|
|
95
|
+
/**
|
|
96
|
+
* The agent principal recorded with this key, when one was — WHO the key
|
|
97
|
+
* is, not who the pod acts as. For recognising the agent's own posts
|
|
98
|
+
* (`resolveAgentUserId`), never sent as the acting user.
|
|
99
|
+
*/
|
|
100
|
+
agentUserId?: string;
|
|
80
101
|
/** Active workspace — from `synap use`, pod default, or env var. Undefined if none configured. */
|
|
81
102
|
workspaceId?: string;
|
|
82
103
|
/** Active project — from `synap project use`, pod default, or env var. Peer of workspaceId; independent. */
|
|
@@ -116,7 +137,7 @@ export declare function attachActiveSessionId(sessionId: string): SessionAttachT
|
|
|
116
137
|
* which reads as the command failing.
|
|
117
138
|
*/
|
|
118
139
|
export declare function detachActiveSessionId(podUrl?: string): string | undefined;
|
|
119
|
-
export declare function hubGet(path: string, params: Record<string, string | number | undefined>, cfg: HubConfig): Promise<unknown>;
|
|
140
|
+
export declare function hubGet(path: string, params: Record<string, string | number | undefined>, cfg: HubConfig, timeoutMs?: number): Promise<unknown>;
|
|
120
141
|
export declare function hubPost(path: string, body: unknown, cfg: HubConfig, timeoutMs?: number): Promise<unknown>;
|
|
121
142
|
/**
|
|
122
143
|
* Multipart POST for Hub doors that accept file uploads (e.g. source-file).
|
package/dist/lib/hub-client.js
CHANGED
|
@@ -3,10 +3,11 @@ import { join } from "node:path";
|
|
|
3
3
|
import { getActivePodConfig, getActiveWorkspaceId, getActiveWorkspaceIdForPod, findPodNameByUrl, getActiveProjectBinding, getActiveProjectId, listPodProfiles, getPodOverride, getSurfaceAgentKey, } from "./pod.js";
|
|
4
4
|
import { resolveAgentOverride } from "./agents-config.js";
|
|
5
5
|
import { resolveActiveLensForPod, getClaudeSessionId, writeLens, clearLensField, } from "./session-lens.js";
|
|
6
|
-
import { conversationHeaders } from "./conversation-headers.js";
|
|
6
|
+
import { clientHeader, conversationHeaders } from "./conversation-headers.js";
|
|
7
7
|
import { effectiveDirectoryLens, resolveDirectoryLensForPod, writeDirectoryLens, clearDirectoryLensField, } from "./directory-lens.js";
|
|
8
8
|
import { preferClaudeCodeSurfaceKey } from "./key-source.js";
|
|
9
9
|
import { samePodOrigin } from "./project-ref.js";
|
|
10
|
+
import { PodMovedError, podFetch } from "./pod-moved.js";
|
|
10
11
|
import { HubRestClient } from "@synap-core/hub-rest-client";
|
|
11
12
|
import { log } from "../utils/logger.js";
|
|
12
13
|
import chalk from "chalk";
|
|
@@ -277,44 +278,48 @@ function hubError(method, path, status, rawBody, suffix, ctx) {
|
|
|
277
278
|
workspaceId: ctx?.workspaceId,
|
|
278
279
|
});
|
|
279
280
|
}
|
|
280
|
-
/**
|
|
281
|
+
/**
|
|
282
|
+
* Where to send users who hit a wall. The support door is the ONE customer
|
|
283
|
+
* support entry point shared by every Synap surface (`from=cli` tells support
|
|
284
|
+
* which surface the person came from); Discord stays as the community option.
|
|
285
|
+
*/
|
|
286
|
+
export const SUPPORT_URL = "https://synap.live/support?from=cli";
|
|
281
287
|
const DISCORD_INVITE_URL = "https://discord.gg/xhRdQ7hG5h";
|
|
282
288
|
/** Wrap a URL in an OSC-8 terminal hyperlink so `label` is clickable. */
|
|
283
289
|
function osc8Link(url, label) {
|
|
284
290
|
return `\x1b]8;;${url}\x1b\\${label}\x1b]8;;\x1b\\`;
|
|
285
291
|
}
|
|
286
292
|
/**
|
|
287
|
-
* Once-per-process guard for the
|
|
293
|
+
* Once-per-process guard for the help footer. A single CLI invocation
|
|
288
294
|
* runs one command; printing the footer at most once keeps it a footer, not a
|
|
289
295
|
* per-error/per-line spam.
|
|
290
296
|
*/
|
|
291
|
-
let
|
|
297
|
+
let _helpShown = false;
|
|
292
298
|
/**
|
|
293
|
-
* Append a one-line "need help?" footer
|
|
294
|
-
* to stderr (same stream as the error it
|
|
295
|
-
* and only once per process.
|
|
296
|
-
* terminal, a plain URL when the stream is
|
|
299
|
+
* Append a one-line "need help?" footer naming the support door (open a ticket)
|
|
300
|
+
* and the Discord invite. Prints to stderr (same stream as the error it
|
|
301
|
+
* follows, so `2>/dev/null` hides both) and only once per process. Each URL is
|
|
302
|
+
* an OSC-8 hyperlink on an interactive terminal, a plain URL when the stream is
|
|
303
|
+
* redirected/piped.
|
|
297
304
|
*/
|
|
298
|
-
export function
|
|
299
|
-
if (
|
|
305
|
+
export function helpFooterLine() {
|
|
306
|
+
if (_helpShown)
|
|
300
307
|
return;
|
|
301
|
-
|
|
302
|
-
const link = process.stderr.isTTY
|
|
303
|
-
|
|
304
|
-
: DISCORD_INVITE_URL;
|
|
305
|
-
console.error(chalk.dim(` Need help? Join our Discord: ${link}`));
|
|
308
|
+
_helpShown = true;
|
|
309
|
+
const link = (url) => (process.stderr.isTTY ? osc8Link(url, url) : url);
|
|
310
|
+
console.error(chalk.dim(` Need help? Open a ticket: ${link(SUPPORT_URL)} (or join our Discord: ${link(DISCORD_INVITE_URL)})`));
|
|
306
311
|
}
|
|
307
312
|
/**
|
|
308
313
|
* Print a failed Hub call: its humanized message plus a hint for the classes
|
|
309
314
|
* where the cause is not obvious from the message alone. Lives here rather than
|
|
310
315
|
* in utils/logger so the logger stays domain-agnostic and the taxonomy and its
|
|
311
|
-
* presentation sit in one file. Always closes with the one-time
|
|
316
|
+
* presentation sit in one file. Always closes with the one-time help footer.
|
|
312
317
|
*/
|
|
313
318
|
export function renderHubError(err) {
|
|
314
319
|
renderHubErrorBody(err);
|
|
315
320
|
// ONE central place every failed Hub command flows through — append the help
|
|
316
321
|
// footer here so it prints once per failed command (guarded), not per line.
|
|
317
|
-
|
|
322
|
+
helpFooterLine();
|
|
318
323
|
}
|
|
319
324
|
function renderHubErrorBody(err) {
|
|
320
325
|
// Hints use log.hint (stderr), not log.dim (stdout): a hint belongs to the
|
|
@@ -325,6 +330,11 @@ function renderHubErrorBody(err) {
|
|
|
325
330
|
log.hint(err.hint);
|
|
326
331
|
return;
|
|
327
332
|
}
|
|
333
|
+
if (err instanceof PodMovedError) {
|
|
334
|
+
// One line: what happened and what to run.
|
|
335
|
+
log.error(err.message);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
328
338
|
if (!(err instanceof HubError)) {
|
|
329
339
|
log.error(err instanceof Error ? err.message : String(err));
|
|
330
340
|
return;
|
|
@@ -475,8 +485,9 @@ export async function resolveHubConfig(opts) {
|
|
|
475
485
|
return {
|
|
476
486
|
podUrl: podOverride.podUrl,
|
|
477
487
|
apiKey: podOverride.hubApiKey,
|
|
478
|
-
//
|
|
479
|
-
userId:
|
|
488
|
+
// "cli": resolveUserId asks the pod who this key acts as (see HubConfig.userId).
|
|
489
|
+
userId: "cli",
|
|
490
|
+
agentUserId: podOverride.agentUserId || undefined,
|
|
480
491
|
workspaceId: safeWs,
|
|
481
492
|
projectId: resolveProjectIdForPod(podOverride.podUrl, {}),
|
|
482
493
|
};
|
|
@@ -499,10 +510,13 @@ export async function resolveHubConfig(opts) {
|
|
|
499
510
|
return {
|
|
500
511
|
podUrl,
|
|
501
512
|
apiKey: agentOverride.apiKey,
|
|
502
|
-
// The
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
513
|
+
// The acting user is asked of the pod ("cli" → /users/me): an agent key
|
|
514
|
+
// acts as its linked human, and the pod 403s the agent's own id.
|
|
515
|
+
userId: "cli",
|
|
516
|
+
// The AGENT's own id, recorded with its key — for `synap worker` to
|
|
517
|
+
// recognise its own room posts (resolveAgentUserId). Never the pod
|
|
518
|
+
// profile name (it once was, and the worker resumed itself on them).
|
|
519
|
+
agentUserId: agentOverride.agentUserId || undefined,
|
|
506
520
|
scopes: envScopes ? envScopes.split(",") : undefined,
|
|
507
521
|
};
|
|
508
522
|
}
|
|
@@ -513,7 +527,13 @@ export async function resolveHubConfig(opts) {
|
|
|
513
527
|
const envScopes = process.env.SYNAP_KEY_SCOPES;
|
|
514
528
|
const envWorkspace = process.env.SYNAP_WORKSPACE_ID;
|
|
515
529
|
const envProject = process.env.SYNAP_PROJECT_ID;
|
|
516
|
-
|
|
530
|
+
// Pod + key are enough. SYNAP_USER_ID used to be required too, so a shell
|
|
531
|
+
// with just the pair (`SYNAP_POD_URL=… SYNAP_HUB_API_KEY=… synap app
|
|
532
|
+
// connect`, the documented way to try another key) fell through to "No pod
|
|
533
|
+
// configured" (2026-10-20). Unset, userId is "cli" and resolveUserId asks
|
|
534
|
+
// the pod (/users/me) — the same fallback --pod and SYNAP_AGENT use. The
|
|
535
|
+
// claude-code surface-key preference below still applies.
|
|
536
|
+
if (envPod && envKey) {
|
|
517
537
|
// Surface-bound resolution guard: if this pod has a `claude-code` surface
|
|
518
538
|
// key pinned to it (via `synap connect --target=claude-code`) and the
|
|
519
539
|
// ambient SYNAP_HUB_API_KEY doesn't match it, the session inherited a
|
|
@@ -534,6 +554,7 @@ export async function resolveHubConfig(opts) {
|
|
|
534
554
|
}
|
|
535
555
|
const apiKey = preferred.apiKey;
|
|
536
556
|
const userId = preferred.userId;
|
|
557
|
+
const agentUserId = preferred.agentUserId;
|
|
537
558
|
// Per-Claude-session lens wins over the global env var, so concurrent
|
|
538
559
|
// sessions can each be scoped to a different workspace — but ONLY when the
|
|
539
560
|
// lens belongs to THIS pod. A lens stamped with another pod is ignored so
|
|
@@ -550,6 +571,7 @@ export async function resolveHubConfig(opts) {
|
|
|
550
571
|
podUrl: envPod,
|
|
551
572
|
apiKey,
|
|
552
573
|
userId,
|
|
574
|
+
agentUserId,
|
|
553
575
|
// The global-config fallback is resolved AGAINST THIS POD (envPod) —
|
|
554
576
|
// never send a workspace that belongs to a different pod.
|
|
555
577
|
workspaceId: lens?.workspaceId ||
|
|
@@ -580,7 +602,10 @@ export async function resolveHubConfig(opts) {
|
|
|
580
602
|
return {
|
|
581
603
|
podUrl: config.podUrl,
|
|
582
604
|
apiKey: config.hubApiKey,
|
|
583
|
-
|
|
605
|
+
// "cli": resolveUserId asks the pod. A profile's agentUserId is the key's
|
|
606
|
+
// principal (a computer or agent key), not who the pod acts as.
|
|
607
|
+
userId: "cli",
|
|
608
|
+
agentUserId: config.agentUserId || undefined,
|
|
584
609
|
// session lens › directory lens › global config. (No env rung here: this
|
|
585
610
|
// branch is only reached when SYNAP_POD_URL is unset.)
|
|
586
611
|
workspaceId: activeLens?.workspaceId || activeDirLens?.workspaceId || getActiveWorkspaceId(),
|
|
@@ -696,13 +721,18 @@ export function detachActiveSessionId(podUrl) {
|
|
|
696
721
|
return previous;
|
|
697
722
|
}
|
|
698
723
|
/**
|
|
699
|
-
* Per-request headers:
|
|
724
|
+
* Per-request headers: which client calls (`synap-cli/<version>`), the focus
|
|
725
|
+
* session this terminal is attached to, and the
|
|
700
726
|
* Claude Code conversation the call comes from (`conversation-headers.ts`) — so
|
|
701
727
|
* the work it lands can be traced back to, and reopened in, that conversation.
|
|
702
728
|
*/
|
|
703
729
|
function sessionHeaders(cfg) {
|
|
704
730
|
const id = resolveActiveSessionId(cfg?.podUrl);
|
|
705
|
-
return {
|
|
731
|
+
return {
|
|
732
|
+
...clientHeader("synap-cli"),
|
|
733
|
+
...conversationHeaders(getClaudeSessionId()),
|
|
734
|
+
...(id ? { "X-Session-Id": id } : {}),
|
|
735
|
+
};
|
|
706
736
|
}
|
|
707
737
|
/** Max 429 retries for bulk import (each waits up to the rate-limit window). */
|
|
708
738
|
const HUB_429_MAX_RETRIES = 12;
|
|
@@ -786,9 +816,11 @@ async function hubFetchWithRetry(url, init, label, timeoutMs) {
|
|
|
786
816
|
await acquireHubSlot();
|
|
787
817
|
let res;
|
|
788
818
|
try {
|
|
789
|
-
res = await
|
|
819
|
+
res = await podFetch(url, init);
|
|
790
820
|
}
|
|
791
821
|
catch (err) {
|
|
822
|
+
if (err instanceof PodMovedError)
|
|
823
|
+
throw err;
|
|
792
824
|
throw networkError(err, url, labelMethod, labelPath, timeoutMs);
|
|
793
825
|
}
|
|
794
826
|
if (res.status !== 429)
|
|
@@ -804,7 +836,10 @@ async function hubFetchWithRetry(url, init, label, timeoutMs) {
|
|
|
804
836
|
await sleep(wait);
|
|
805
837
|
}
|
|
806
838
|
}
|
|
807
|
-
export async function hubGet(path, params, cfg
|
|
839
|
+
export async function hubGet(path, params, cfg,
|
|
840
|
+
// Default 15s suits reads. A long-poll door (`/answers/wait`) holds the
|
|
841
|
+
// request open for its own `timeoutSeconds`; its caller passes more.
|
|
842
|
+
timeoutMs = 15_000) {
|
|
808
843
|
const url = new URL(`${cfg.podUrl}/api/hub${path}`);
|
|
809
844
|
for (const [k, v] of Object.entries(params)) {
|
|
810
845
|
if (v !== undefined)
|
|
@@ -812,8 +847,8 @@ export async function hubGet(path, params, cfg) {
|
|
|
812
847
|
}
|
|
813
848
|
const res = await hubFetchWithRetry(url.toString(), {
|
|
814
849
|
headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
|
|
815
|
-
signal: AbortSignal.timeout(
|
|
816
|
-
}, `GET ${path}`,
|
|
850
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
851
|
+
}, `GET ${path}`, timeoutMs);
|
|
817
852
|
if (!res.ok) {
|
|
818
853
|
const body = await res.text().catch(() => "");
|
|
819
854
|
throw hubError("GET", path, res.status, body, undefined, cfg);
|
|
@@ -887,7 +922,7 @@ export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs
|
|
|
887
922
|
const url = `${cfg.podUrl}/api/hub${path}`;
|
|
888
923
|
let res;
|
|
889
924
|
try {
|
|
890
|
-
res = await
|
|
925
|
+
res = await podFetch(url, {
|
|
891
926
|
method: "POST",
|
|
892
927
|
headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
|
|
893
928
|
body: form,
|
|
@@ -895,6 +930,8 @@ export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs
|
|
|
895
930
|
});
|
|
896
931
|
}
|
|
897
932
|
catch (err) {
|
|
933
|
+
if (err instanceof PodMovedError)
|
|
934
|
+
throw err;
|
|
898
935
|
throw networkError(err, url, "POST multipart", path, timeoutMs);
|
|
899
936
|
}
|
|
900
937
|
if (res.status !== 429) {
|
|
@@ -919,7 +956,7 @@ export async function hubPatch(path, body, cfg) {
|
|
|
919
956
|
const url = `${cfg.podUrl}/api/hub${path}`;
|
|
920
957
|
let res;
|
|
921
958
|
try {
|
|
922
|
-
res = await
|
|
959
|
+
res = await podFetch(url, {
|
|
923
960
|
method: "PATCH",
|
|
924
961
|
headers: { Authorization: `Bearer ${cfg.apiKey}`, "Content-Type": "application/json", ...sessionHeaders(cfg) },
|
|
925
962
|
body: JSON.stringify(body),
|
|
@@ -927,6 +964,8 @@ export async function hubPatch(path, body, cfg) {
|
|
|
927
964
|
});
|
|
928
965
|
}
|
|
929
966
|
catch (err) {
|
|
967
|
+
if (err instanceof PodMovedError)
|
|
968
|
+
throw err;
|
|
930
969
|
throw networkError(err, url, "PATCH", path, 15_000);
|
|
931
970
|
}
|
|
932
971
|
if (!res.ok) {
|
|
@@ -942,13 +981,15 @@ export async function hubDelete(path, cfg) {
|
|
|
942
981
|
const url = `${cfg.podUrl}/api/hub${path}`;
|
|
943
982
|
let res;
|
|
944
983
|
try {
|
|
945
|
-
res = await
|
|
984
|
+
res = await podFetch(url, {
|
|
946
985
|
method: "DELETE",
|
|
947
986
|
headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
|
|
948
987
|
signal: AbortSignal.timeout(15_000),
|
|
949
988
|
});
|
|
950
989
|
}
|
|
951
990
|
catch (err) {
|
|
991
|
+
if (err instanceof PodMovedError)
|
|
992
|
+
throw err;
|
|
952
993
|
throw networkError(err, url, "DELETE", path, 15_000);
|
|
953
994
|
}
|
|
954
995
|
if (!res.ok) {
|