totemlayer 0.1.21 → 0.1.24
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 +29 -29
- package/bin/agent-runner.mjs +26 -2
- package/bin/agent-setup.mjs +21 -3
- package/bin/automatic-runtime.mjs +44 -6
- package/bin/bound-login.mjs +107 -0
- package/bin/connected-agent-service.mjs +115 -78
- package/bin/device-login.mjs +100 -8
- package/bin/installation.mjs +129 -74
- package/bin/mission-agent-service.mjs +23 -5
- package/bin/mission-coordination.mjs +311 -0
- package/bin/mission-worker.mjs +271 -20
- package/bin/model-adapter.mjs +30 -8
- package/bin/network-runtime.mjs +98 -9
- package/bin/runtime-diagnostics.mjs +4 -0
- package/bin/runtime-installation.mjs +1 -0
- package/bin/sdk/action-types.d.ts +236 -0
- package/bin/sdk/actions.d.ts +19 -0
- package/bin/sdk/errors.d.ts +30 -0
- package/bin/sdk/errors.js +14 -1
- package/bin/sdk/index.d.ts +298 -0
- package/bin/sdk/index.js +55 -11
- package/bin/sdk/inspector.d.ts +40 -0
- package/bin/sdk/request-options.d.ts +13 -0
- package/bin/sdk/transport.d.ts +24 -0
- package/bin/sdk/types.d.ts +638 -0
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
- package/scripts/prepare.mjs +9 -20
package/bin/network-runtime.mjs
CHANGED
|
@@ -608,7 +608,25 @@ export async function runRuntime(settings) {
|
|
|
608
608
|
const directory = join(settings.stateDir ?? join(homedir(), ".totem", "runtime"), scope);
|
|
609
609
|
const release = await runtimeLock(directory);
|
|
610
610
|
const journal = new Journal(directory);
|
|
611
|
+
const runId = settings.runId ?? randomUUID();
|
|
612
|
+
const verify = typeof client.runtime?.start === "function";
|
|
613
|
+
if (settings.verifyReadiness && !verify) {
|
|
614
|
+
await release();
|
|
615
|
+
throw Object.assign(new Error("Upgrade the Totem SDK to test and authorize this listener."), {
|
|
616
|
+
code: "READINESS_UNSUPPORTED",
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
if (verify && settings.codexThreadId) {
|
|
620
|
+
await release();
|
|
621
|
+
throw Object.assign(
|
|
622
|
+
new Error(
|
|
623
|
+
"Existing Codex chat queueing cannot verify a controlled receive/wake/reply round trip. Configure the isolated Codex adapter instead.",
|
|
624
|
+
),
|
|
625
|
+
{ code: "READINESS_UNSUPPORTED" },
|
|
626
|
+
);
|
|
627
|
+
}
|
|
611
628
|
let heartbeat;
|
|
629
|
+
let probeFailed = false;
|
|
612
630
|
let inbox;
|
|
613
631
|
let humanDone = false;
|
|
614
632
|
let fatalError;
|
|
@@ -618,14 +636,15 @@ export async function runRuntime(settings) {
|
|
|
618
636
|
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
619
637
|
).version;
|
|
620
638
|
const report = (error, context = {}) => {
|
|
639
|
+
const reconnecting = !context.messageId && transientNetworkError(error);
|
|
621
640
|
diagnostics = {
|
|
622
|
-
phase: "blocked",
|
|
641
|
+
phase: reconnecting ? "reconnecting" : "blocked",
|
|
623
642
|
errorCode: runtimeErrorCode(error),
|
|
624
643
|
...(typeof context.messageId === "string" ? { messageId: context.messageId } : {}),
|
|
625
644
|
};
|
|
626
645
|
if (settings.once) process.exitCode = 1;
|
|
627
646
|
emit({
|
|
628
|
-
type: "runtime_error",
|
|
647
|
+
type: reconnecting ? "runtime_reconnecting" : "runtime_error",
|
|
629
648
|
...context,
|
|
630
649
|
error: {
|
|
631
650
|
code: error.code ?? "RUNTIME_FAILED",
|
|
@@ -641,10 +660,56 @@ export async function runRuntime(settings) {
|
|
|
641
660
|
});
|
|
642
661
|
};
|
|
643
662
|
try {
|
|
663
|
+
if (verify) {
|
|
664
|
+
await client.runtime.start(
|
|
665
|
+
{
|
|
666
|
+
runId,
|
|
667
|
+
provider: settings.provider === "ollama" ? "ollama-local" : settings.provider,
|
|
668
|
+
hostKind: settings.hostKind ?? "local",
|
|
669
|
+
},
|
|
670
|
+
{ signal },
|
|
671
|
+
);
|
|
672
|
+
try {
|
|
673
|
+
const probe = await client.runtime.probe(runId, { signal });
|
|
674
|
+
if (
|
|
675
|
+
probe.runId !== runId ||
|
|
676
|
+
probe.protocol !== "totem-readiness-v1" ||
|
|
677
|
+
!/^[a-f0-9]{64}$/.test(probe.challenge)
|
|
678
|
+
)
|
|
679
|
+
throw new Error("The readiness challenge is invalid.");
|
|
680
|
+
if (!probe.completed) {
|
|
681
|
+
const answer = await generate({
|
|
682
|
+
client,
|
|
683
|
+
identity,
|
|
684
|
+
journal,
|
|
685
|
+
key: `readiness:${runId}:${probe.challenge}`,
|
|
686
|
+
text: `Controlled readiness check. Reply with exactly TOTEM_READY:${probe.challenge} and no other text. No tools, files, external actions or business work are authorized.`,
|
|
687
|
+
history: [],
|
|
688
|
+
settings: { ...settings, system: "", allowAgents: [], modelTimeoutMs: 30000 },
|
|
689
|
+
signal,
|
|
690
|
+
allowTools: false,
|
|
691
|
+
});
|
|
692
|
+
await client.runtime.replyProbe({ runId, reply: String(answer).trim() }, { signal });
|
|
693
|
+
}
|
|
694
|
+
emit({ type: "runtime_ready", data: { runId, provider: settings.provider, ready: true } });
|
|
695
|
+
} catch (error) {
|
|
696
|
+
probeFailed = true;
|
|
697
|
+
await client.runtime
|
|
698
|
+
.heartbeat({ runId, state: "error", errorCode: "READINESS_PROBE_FAILED" }, { signal })
|
|
699
|
+
.catch(() => {});
|
|
700
|
+
throw Object.assign(
|
|
701
|
+
new Error(
|
|
702
|
+
"The bounded receive/wake/reply check failed. Check the selected adapter and its authorization, then restart this listener.",
|
|
703
|
+
),
|
|
704
|
+
{ code: "READINESS_PROBE_FAILED", cause: error },
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
644
708
|
const beat = async () => {
|
|
645
709
|
const result = await client.runtime.heartbeat(
|
|
646
710
|
{
|
|
647
711
|
label: `${settings.provider}:${settings.model}`.slice(0, 100),
|
|
712
|
+
...(verify ? { runId, state: "online" } : {}),
|
|
648
713
|
...(diagnosticsSupported
|
|
649
714
|
? {
|
|
650
715
|
diagnostics: {
|
|
@@ -672,7 +737,7 @@ export async function runRuntime(settings) {
|
|
|
672
737
|
} catch (error) {
|
|
673
738
|
if (signal.aborted) break;
|
|
674
739
|
report(error);
|
|
675
|
-
if (error.status === 401 || error.status === 403) {
|
|
740
|
+
if (error.status === 401 || error.status === 403 || error.code === "RUNTIME_SUPERSEDED") {
|
|
676
741
|
fatalError = error;
|
|
677
742
|
shutdown.abort();
|
|
678
743
|
break;
|
|
@@ -688,6 +753,7 @@ export async function runRuntime(settings) {
|
|
|
688
753
|
model: settings.model,
|
|
689
754
|
allowedAgents: settings.allowAgents,
|
|
690
755
|
inspection: settings.inspector.mode,
|
|
756
|
+
ready: verify,
|
|
691
757
|
},
|
|
692
758
|
});
|
|
693
759
|
diagnostics = { phase: "waiting" };
|
|
@@ -700,8 +766,17 @@ export async function runRuntime(settings) {
|
|
|
700
766
|
once: settings.once,
|
|
701
767
|
onPollError: (error) => report(error),
|
|
702
768
|
shouldContinue: () => !settings.once || !humanDone,
|
|
703
|
-
messages: (signal)
|
|
704
|
-
client.messages.iterate(
|
|
769
|
+
messages: async function* (signal) {
|
|
770
|
+
for await (const message of client.messages.iterate(
|
|
771
|
+
{ direction: "inbound", unread: true, limit: 100 },
|
|
772
|
+
{ signal },
|
|
773
|
+
))
|
|
774
|
+
yield message;
|
|
775
|
+
if (diagnostics.phase === "reconnecting") {
|
|
776
|
+
diagnostics = { phase: "waiting" };
|
|
777
|
+
emit({ type: "runtime_resumed" });
|
|
778
|
+
}
|
|
779
|
+
},
|
|
705
780
|
eligible: async (message) => {
|
|
706
781
|
const blocker = await journal.read(`message-blocker:${message.id}`);
|
|
707
782
|
// Older runtimes persisted inspector outages as permanent blockers.
|
|
@@ -713,13 +788,22 @@ export async function runRuntime(settings) {
|
|
|
713
788
|
diagnostics = { phase: "working", messageId: message.id };
|
|
714
789
|
emit({ type: "message_started", data: { messageId: message.id } });
|
|
715
790
|
if (settings.codexThreadId) {
|
|
716
|
-
const delivery = await queueCodexChatMessage({
|
|
791
|
+
const delivery = await queueCodexChatMessage({
|
|
792
|
+
settings,
|
|
793
|
+
client,
|
|
794
|
+
identity,
|
|
795
|
+
journal,
|
|
796
|
+
message,
|
|
797
|
+
signal,
|
|
798
|
+
});
|
|
717
799
|
if (delivery.state === "uncertain") {
|
|
718
800
|
report({ code: "AGENT_OUTCOME_UNCERTAIN" }, { messageId: message.id });
|
|
719
801
|
} else {
|
|
720
802
|
diagnostics = { phase: "waiting" };
|
|
721
|
-
emit({
|
|
722
|
-
|
|
803
|
+
emit({
|
|
804
|
+
type: delivery.state === "confirmed" ? "message_handled" : "chat_queued",
|
|
805
|
+
data: { messageId: message.id, threadId: settings.codexThreadId },
|
|
806
|
+
});
|
|
723
807
|
}
|
|
724
808
|
return;
|
|
725
809
|
}
|
|
@@ -871,7 +955,8 @@ export async function runRuntime(settings) {
|
|
|
871
955
|
let leaseError;
|
|
872
956
|
const renewal = (async () => {
|
|
873
957
|
while (!commandSignal.aborted) {
|
|
874
|
-
|
|
958
|
+
// Observe human interruption promptly without repeating provider execution.
|
|
959
|
+
await pause(5000, commandSignal);
|
|
875
960
|
try {
|
|
876
961
|
await client.chatCommands.renew(command.id, claim.leaseToken, {
|
|
877
962
|
signal: commandSignal,
|
|
@@ -980,6 +1065,10 @@ export async function runRuntime(settings) {
|
|
|
980
1065
|
shutdown.abort();
|
|
981
1066
|
await heartbeat;
|
|
982
1067
|
await inbox;
|
|
1068
|
+
if (verify && !probeFailed)
|
|
1069
|
+
await client.runtime
|
|
1070
|
+
.heartbeat({ runId, state: "offline" }, { signal: AbortSignal.timeout(2000) })
|
|
1071
|
+
.catch(() => {});
|
|
983
1072
|
await release();
|
|
984
1073
|
emit({ type: "runtime_stopped", data: { agent: identity.address } });
|
|
985
1074
|
}
|
|
@@ -32,6 +32,10 @@ export const RUNTIME_ERROR_CODES = new Set([
|
|
|
32
32
|
"AGENT_IDENTITY_MISMATCH",
|
|
33
33
|
"MODEL_ERROR",
|
|
34
34
|
"MAX_STEPS",
|
|
35
|
+
"READINESS_UNSUPPORTED",
|
|
36
|
+
"READINESS_PROBE_FAILED",
|
|
37
|
+
"RUNTIME_AUTHORIZATION_REJECTED",
|
|
38
|
+
"RUNTIME_SUPERSEDED",
|
|
35
39
|
"INTERNAL_ERROR",
|
|
36
40
|
]);
|
|
37
41
|
export function runtimeErrorCode(error) {
|
|
@@ -251,6 +251,7 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
251
251
|
"TOTEM_INSPECTOR_MODEL",
|
|
252
252
|
"TOTEM_INSPECTOR_TIMEOUT_MS",
|
|
253
253
|
"TOTEM_PROVIDER",
|
|
254
|
+
"TOTEM_RUNTIME_HOST_KIND",
|
|
254
255
|
"TOTEM_MODEL",
|
|
255
256
|
"TOTEM_BASE_URL",
|
|
256
257
|
"TOTEM_AGENT_ID",
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { JsonValue, RequestOptions, TotemOptions } from "./types.js";
|
|
2
|
+
export interface TotemActionsOptions extends Omit<TotemOptions, "apiKey" | "address"> {
|
|
3
|
+
/** Explicitly separate scoped Action Checks credential; never a Network tn_ key. */
|
|
4
|
+
actionChecksApiKey: string;
|
|
5
|
+
}
|
|
6
|
+
export interface AssistantCheckOptions extends RequestOptions {
|
|
7
|
+
/** Same scoped credential used by the separate TotemActions client. */
|
|
8
|
+
actionChecksApiKey: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AssistantActionCheck {
|
|
11
|
+
suite: ActionCheckSuite;
|
|
12
|
+
/** Use this exact returned action for grant issuance and execution. */
|
|
13
|
+
action: ActionProposal;
|
|
14
|
+
missionId: string;
|
|
15
|
+
workItemId: string;
|
|
16
|
+
executionAuthorized: false;
|
|
17
|
+
checksUrl: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AssistantActionReceipt {
|
|
20
|
+
id: string;
|
|
21
|
+
outcome: ActionExecutionReceipt["outcome"];
|
|
22
|
+
reconciliationState: ActionExecutionReceipt["reconciliationStatus"];
|
|
23
|
+
executorAdapter: string;
|
|
24
|
+
completedAt: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface AssistantActionCheckList {
|
|
27
|
+
checks: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
/** Original private handoff suite, verified through immutable rerun lineage. */
|
|
30
|
+
handoffSuiteId?: string;
|
|
31
|
+
actionDigest: string;
|
|
32
|
+
status: ActionCheckSuite["status"];
|
|
33
|
+
conclusion: ActionSuiteConclusion | null;
|
|
34
|
+
expiresAt: string;
|
|
35
|
+
staleAt: string | null;
|
|
36
|
+
executionReceipts: AssistantActionReceipt[];
|
|
37
|
+
checksUrl: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export interface AssistantActionReceiptResult {
|
|
41
|
+
receipt: AssistantActionReceipt & {
|
|
42
|
+
receiptDigest: string;
|
|
43
|
+
};
|
|
44
|
+
missionId: string;
|
|
45
|
+
workItemId: string;
|
|
46
|
+
}
|
|
47
|
+
export type ActionSideEffectClass = "READ_ONLY" | "DATA_EGRESS" | "EXTERNAL_COMMUNICATION" | "FINANCIAL" | "DESTRUCTIVE" | "AUTHORITY_CHANGE" | "SECURITY_OPERATION";
|
|
48
|
+
export type ActionReversibility = "REVERSIBLE" | "COMPENSATABLE" | "IRREVERSIBLE" | "UNKNOWN";
|
|
49
|
+
export type ActionDataClassification = "PUBLIC" | "INTERNAL" | "CONFIDENTIAL" | "PERSONAL" | "FINANCIAL" | "AUTHENTICATION" | "SOURCE_CODE" | "SECRET";
|
|
50
|
+
export type ActionExecutorAdapter = "NOOP_READ" | "LOOPBACK_ECHO" | "FAKE_LEDGER" | "TEMP_FIXTURE_DELETE" | "SUPPLYCO_QUOTE" | "CREDENTIAL_ISSUER" | "RECEIVER_API_KEY" | "CREDENTIAL_VERIFY";
|
|
51
|
+
export interface ActionFinancialContext {
|
|
52
|
+
actionType: "PAYMENT" | "PURCHASE" | "TRANSFER" | "REFUND" | "WITHDRAWAL" | "DEPOSIT" | "TRADE" | "SUBSCRIPTION" | "INVOICE_PAYMENT" | "CRYPTO" | "QUOTE_REQUEST";
|
|
53
|
+
movesValue: boolean;
|
|
54
|
+
amount: number | null;
|
|
55
|
+
currency: string | null;
|
|
56
|
+
sourceAccount: string | null;
|
|
57
|
+
beneficiary: string | null;
|
|
58
|
+
destinationAccount: string | null;
|
|
59
|
+
beneficiaryIsNew: boolean | null;
|
|
60
|
+
reversible: boolean | null;
|
|
61
|
+
}
|
|
62
|
+
/** Exact proposed action. The server derives tenant authority from the scoped credential. */
|
|
63
|
+
export interface ActionProposal {
|
|
64
|
+
version: "1";
|
|
65
|
+
/** Totem identity-agent ID from assistant.context().agent.identityAgentId. */
|
|
66
|
+
agentId: string;
|
|
67
|
+
credentialId?: string | null;
|
|
68
|
+
automationRunId?: string | null;
|
|
69
|
+
toolName: string;
|
|
70
|
+
actionType: string;
|
|
71
|
+
sideEffectClass: ActionSideEffectClass;
|
|
72
|
+
executorAdapter: ActionExecutorAdapter;
|
|
73
|
+
target: {
|
|
74
|
+
resourceType: string;
|
|
75
|
+
resourceId?: string | null;
|
|
76
|
+
ownedTemporaryFixture?: boolean;
|
|
77
|
+
};
|
|
78
|
+
destination?: {
|
|
79
|
+
protocol: "local:" | "http:" | "https:";
|
|
80
|
+
host: string;
|
|
81
|
+
port?: number | null;
|
|
82
|
+
path: string;
|
|
83
|
+
tenant?: string | null;
|
|
84
|
+
redirects?: "DENY" | "SAME_ORIGIN";
|
|
85
|
+
} | null;
|
|
86
|
+
data: {
|
|
87
|
+
classifications?: ActionDataClassification[];
|
|
88
|
+
fields?: string[];
|
|
89
|
+
payload?: JsonValue;
|
|
90
|
+
minimized?: boolean;
|
|
91
|
+
byteLength?: number | null;
|
|
92
|
+
};
|
|
93
|
+
financial?: ActionFinancialContext | null;
|
|
94
|
+
reversibility: ActionReversibility;
|
|
95
|
+
justification: string;
|
|
96
|
+
provenance: Array<{
|
|
97
|
+
kind: "AUTHENTICATED_USER" | "TRUSTED_POLICY" | "INTERNAL_DATA" | "TOOL_OUTPUT" | "WEB_CONTENT" | "EMAIL" | "MESSAGE" | "OTHER_AGENT" | "UNTRUSTED_TEXT";
|
|
98
|
+
/** A caller-provided flag is advisory; the server derives source trust. */
|
|
99
|
+
trusted: boolean;
|
|
100
|
+
reference?: string | null;
|
|
101
|
+
content?: string | null;
|
|
102
|
+
}>;
|
|
103
|
+
idempotencyKey: string;
|
|
104
|
+
executionAttemptId: string;
|
|
105
|
+
expiresAt: string;
|
|
106
|
+
payload?: JsonValue;
|
|
107
|
+
}
|
|
108
|
+
export type ActionCheckConclusion = "PASS" | "WARN" | "REQUIRE_APPROVAL" | "BLOCK" | "ERROR" | "SKIPPED";
|
|
109
|
+
export type ActionSuiteConclusion = "ALLOW" | "ALLOW_WITH_WARNINGS" | "REQUIRES_APPROVAL" | "BLOCK" | "ERROR";
|
|
110
|
+
export type ActionRiskLevel = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
111
|
+
export interface ActionExecutionReceipt {
|
|
112
|
+
id: string;
|
|
113
|
+
outcome: "SUCCEEDED" | "FAILED" | "UNKNOWN" | "COMPENSATED";
|
|
114
|
+
executorAdapter: string;
|
|
115
|
+
externalResponseSummary: string | null;
|
|
116
|
+
idempotencyStatus: "RECORDED";
|
|
117
|
+
reconciliationStatus: "NOT_REQUIRED" | "PENDING" | "CONFIRMED" | "FAILED";
|
|
118
|
+
compensationStatus: "RECORDED" | "NOT_REQUIRED";
|
|
119
|
+
createdAt: string;
|
|
120
|
+
executedAt: string | null;
|
|
121
|
+
completedAt: string | null;
|
|
122
|
+
}
|
|
123
|
+
/** Redacted server view; do not reconstruct an executable action from this projection. */
|
|
124
|
+
export interface ActionCheckSuite {
|
|
125
|
+
id: string;
|
|
126
|
+
status: "QUEUED" | "RUNNING" | "COMPLETED" | "STALE";
|
|
127
|
+
conclusion: ActionSuiteConclusion | null;
|
|
128
|
+
riskLevel: ActionRiskLevel;
|
|
129
|
+
explanation: string | null;
|
|
130
|
+
policyVersion: string;
|
|
131
|
+
requiredProviders: string[];
|
|
132
|
+
actionDigest: string;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
updatedAt: string;
|
|
135
|
+
startedAt: string | null;
|
|
136
|
+
completedAt: string | null;
|
|
137
|
+
expiresAt: string;
|
|
138
|
+
staleAt: string | null;
|
|
139
|
+
staleReason: string | null;
|
|
140
|
+
supersededBySuiteId: string | null;
|
|
141
|
+
action: {
|
|
142
|
+
id: string;
|
|
143
|
+
agentId: string;
|
|
144
|
+
agent: {
|
|
145
|
+
id: string;
|
|
146
|
+
name: string;
|
|
147
|
+
} | null;
|
|
148
|
+
toolName: string;
|
|
149
|
+
actionType: string;
|
|
150
|
+
sideEffectClass: ActionSideEffectClass;
|
|
151
|
+
target: {
|
|
152
|
+
resourceType: string;
|
|
153
|
+
resourceId: string | null;
|
|
154
|
+
};
|
|
155
|
+
destination: {
|
|
156
|
+
protocol: string;
|
|
157
|
+
host: string;
|
|
158
|
+
port: number | null;
|
|
159
|
+
path: string;
|
|
160
|
+
tenant: string | null;
|
|
161
|
+
} | null;
|
|
162
|
+
dataClassifications: JsonValue;
|
|
163
|
+
financial: {
|
|
164
|
+
actionType: string;
|
|
165
|
+
movesValue: boolean | null;
|
|
166
|
+
amount: number | null;
|
|
167
|
+
currency: string | null;
|
|
168
|
+
beneficiary: string | null;
|
|
169
|
+
destinationAccount: string | null;
|
|
170
|
+
} | null;
|
|
171
|
+
reversibility: ActionReversibility;
|
|
172
|
+
justification: string;
|
|
173
|
+
expiresAt: string;
|
|
174
|
+
};
|
|
175
|
+
runs: Array<{
|
|
176
|
+
id: string;
|
|
177
|
+
providerName: string;
|
|
178
|
+
providerVersion: string;
|
|
179
|
+
required: boolean;
|
|
180
|
+
status: "QUEUED" | "RUNNING" | "COMPLETED" | "TIMED_OUT";
|
|
181
|
+
conclusion: ActionCheckConclusion | null;
|
|
182
|
+
reasonCode: string | null;
|
|
183
|
+
summary: string | null;
|
|
184
|
+
findings: Array<{
|
|
185
|
+
id: string;
|
|
186
|
+
severity: "INFO" | ActionRiskLevel;
|
|
187
|
+
category: string;
|
|
188
|
+
reasonCode: string;
|
|
189
|
+
explanation: string;
|
|
190
|
+
fieldReference: string | null;
|
|
191
|
+
sensitiveFingerprint: string | null;
|
|
192
|
+
remediation: string | null;
|
|
193
|
+
evidenceSource: string;
|
|
194
|
+
confidence: number;
|
|
195
|
+
}>;
|
|
196
|
+
startedAt: string | null;
|
|
197
|
+
completedAt: string | null;
|
|
198
|
+
timedOutAt: string | null;
|
|
199
|
+
}>;
|
|
200
|
+
approval: {
|
|
201
|
+
id: string;
|
|
202
|
+
status: "APPROVED" | "REJECTED" | "REVOKED" | "EXPIRED";
|
|
203
|
+
reason: string | null;
|
|
204
|
+
approver: {
|
|
205
|
+
id: string;
|
|
206
|
+
name?: string | null;
|
|
207
|
+
email?: string;
|
|
208
|
+
};
|
|
209
|
+
createdAt: string;
|
|
210
|
+
expiresAt: string;
|
|
211
|
+
rejectedAt: string | null;
|
|
212
|
+
revokedAt: string | null;
|
|
213
|
+
} | null;
|
|
214
|
+
grant: {
|
|
215
|
+
id: string;
|
|
216
|
+
status: "ACTIVE" | "CONSUMED" | "REVOKED" | "EXPIRED";
|
|
217
|
+
createdAt: string;
|
|
218
|
+
expiresAt: string;
|
|
219
|
+
consumedAt: string | null;
|
|
220
|
+
invalidatedAt: string | null;
|
|
221
|
+
} | null;
|
|
222
|
+
receipt: ActionExecutionReceipt | null;
|
|
223
|
+
confirmationPhrase: string | null;
|
|
224
|
+
demoAction: ActionProposal | null;
|
|
225
|
+
}
|
|
226
|
+
export interface ActionExecutionGrant {
|
|
227
|
+
/** One-use secret, bound to the checked action and issuing principal. */
|
|
228
|
+
token: string;
|
|
229
|
+
expiresAt: string;
|
|
230
|
+
suite: ActionCheckSuite;
|
|
231
|
+
}
|
|
232
|
+
export interface CheckedExecutionResult {
|
|
233
|
+
suite: ActionCheckSuite;
|
|
234
|
+
receipt: ActionExecutionReceipt | null;
|
|
235
|
+
receiptId: string;
|
|
236
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RequestOptions } from "./types.js";
|
|
2
|
+
import type { ActionCheckSuite, ActionExecutionGrant, ActionProposal, CheckedExecutionResult, TotemActionsOptions } from "./action-types.js";
|
|
3
|
+
export * from "./action-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Separately authenticated Action Checks client. Human approval stays in Totem's UI.
|
|
6
|
+
* Writes are never automatically replayed, even when the action has an idempotency key.
|
|
7
|
+
* After an ambiguous execution failure, use get() to inspect the receipt before proceeding.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TotemActions {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(options: TotemActionsOptions);
|
|
12
|
+
create(action: ActionProposal, options?: RequestOptions): Promise<ActionCheckSuite>;
|
|
13
|
+
get(suiteId: string, options?: RequestOptions): Promise<ActionCheckSuite>;
|
|
14
|
+
issueGrant(suiteId: string, action: ActionProposal, options?: RequestOptions): Promise<ActionExecutionGrant>;
|
|
15
|
+
execute(suiteId: string, input: {
|
|
16
|
+
action: ActionProposal;
|
|
17
|
+
grant: string;
|
|
18
|
+
}, options?: RequestOptions): Promise<CheckedExecutionResult>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare class TotemError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly status?: number;
|
|
4
|
+
readonly requestId?: string;
|
|
5
|
+
readonly retryAfterSeconds?: number;
|
|
6
|
+
constructor(message: string, options: {
|
|
7
|
+
code: string;
|
|
8
|
+
status?: number;
|
|
9
|
+
requestId?: string;
|
|
10
|
+
retryAfterSeconds?: number;
|
|
11
|
+
cause?: unknown;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export declare class TotemAuthenticationError extends TotemError {
|
|
15
|
+
}
|
|
16
|
+
export declare class TotemPermissionError extends TotemError {
|
|
17
|
+
}
|
|
18
|
+
export declare class TotemNotFoundError extends TotemError {
|
|
19
|
+
}
|
|
20
|
+
export declare class TotemConflictError extends TotemError {
|
|
21
|
+
}
|
|
22
|
+
export declare class TotemRateLimitError extends TotemError {
|
|
23
|
+
}
|
|
24
|
+
export declare class TotemTimeoutError extends TotemError {
|
|
25
|
+
}
|
|
26
|
+
export declare class TotemNetworkError extends TotemError {
|
|
27
|
+
}
|
|
28
|
+
export declare class TotemCanceledError extends TotemError {
|
|
29
|
+
}
|
|
30
|
+
export declare function apiError(status: number, body: unknown, requestId?: string): TotemError;
|
package/bin/sdk/errors.js
CHANGED
|
@@ -2,12 +2,14 @@ export class TotemError extends Error {
|
|
|
2
2
|
code;
|
|
3
3
|
status;
|
|
4
4
|
requestId;
|
|
5
|
+
retryAfterSeconds;
|
|
5
6
|
constructor(message, options) {
|
|
6
7
|
super(message, { cause: options.cause });
|
|
7
8
|
this.name = new.target.name;
|
|
8
9
|
this.code = options.code;
|
|
9
10
|
this.status = options.status;
|
|
10
11
|
this.requestId = options.requestId;
|
|
12
|
+
this.retryAfterSeconds = options.retryAfterSeconds;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
export class TotemAuthenticationError extends TotemError {
|
|
@@ -50,5 +52,16 @@ export function apiError(status, body, requestId) {
|
|
|
50
52
|
: status === 429
|
|
51
53
|
? TotemRateLimitError
|
|
52
54
|
: TotemError;
|
|
53
|
-
|
|
55
|
+
const details = "details" in error ? error.details : undefined;
|
|
56
|
+
const retryAfter = details && typeof details === "object" && "retryAfterSeconds" in details
|
|
57
|
+
? details.retryAfterSeconds
|
|
58
|
+
: undefined;
|
|
59
|
+
const retryAfterSeconds = status === 429 &&
|
|
60
|
+
typeof retryAfter === "number" &&
|
|
61
|
+
Number.isSafeInteger(retryAfter) &&
|
|
62
|
+
retryAfter > 0 &&
|
|
63
|
+
retryAfter <= 86400
|
|
64
|
+
? retryAfter
|
|
65
|
+
: undefined;
|
|
66
|
+
return new Constructor(message, { code, status, requestId: id, retryAfterSeconds });
|
|
54
67
|
}
|