totemlayer 0.1.20 → 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 +31 -29
- package/bin/agent-profile.mjs +13 -0
- package/bin/agent-runner.mjs +31 -2
- package/bin/agent-setup.mjs +58 -6
- package/bin/automatic-runtime.mjs +44 -6
- package/bin/bound-login.mjs +107 -0
- package/bin/codex-chat-bridge.mjs +197 -0
- package/bin/codex-desktop-wake.mjs +22 -0
- package/bin/connected-agent-service.mjs +115 -78
- package/bin/device-login.mjs +100 -8
- package/bin/executor-readiness.mjs +97 -20
- 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 +115 -6
- 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
|
@@ -8,6 +8,7 @@ import { runInboxQueue } from "./inbox-queue.mjs";
|
|
|
8
8
|
import { retryNetwork, transientNetworkError } from "./network-retry.mjs";
|
|
9
9
|
import { customerInspector, inspectNetworkMessage } from "./local-inspector.mjs";
|
|
10
10
|
import { runtimeErrorCode } from "./runtime-diagnostics.mjs";
|
|
11
|
+
import { queueCodexChatMessage } from "./codex-chat-bridge.mjs";
|
|
11
12
|
import {
|
|
12
13
|
externalModelReply,
|
|
13
14
|
isExternalModelProvider,
|
|
@@ -607,7 +608,25 @@ export async function runRuntime(settings) {
|
|
|
607
608
|
const directory = join(settings.stateDir ?? join(homedir(), ".totem", "runtime"), scope);
|
|
608
609
|
const release = await runtimeLock(directory);
|
|
609
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
|
+
}
|
|
610
628
|
let heartbeat;
|
|
629
|
+
let probeFailed = false;
|
|
611
630
|
let inbox;
|
|
612
631
|
let humanDone = false;
|
|
613
632
|
let fatalError;
|
|
@@ -617,14 +636,15 @@ export async function runRuntime(settings) {
|
|
|
617
636
|
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
618
637
|
).version;
|
|
619
638
|
const report = (error, context = {}) => {
|
|
639
|
+
const reconnecting = !context.messageId && transientNetworkError(error);
|
|
620
640
|
diagnostics = {
|
|
621
|
-
phase: "blocked",
|
|
641
|
+
phase: reconnecting ? "reconnecting" : "blocked",
|
|
622
642
|
errorCode: runtimeErrorCode(error),
|
|
623
643
|
...(typeof context.messageId === "string" ? { messageId: context.messageId } : {}),
|
|
624
644
|
};
|
|
625
645
|
if (settings.once) process.exitCode = 1;
|
|
626
646
|
emit({
|
|
627
|
-
type: "runtime_error",
|
|
647
|
+
type: reconnecting ? "runtime_reconnecting" : "runtime_error",
|
|
628
648
|
...context,
|
|
629
649
|
error: {
|
|
630
650
|
code: error.code ?? "RUNTIME_FAILED",
|
|
@@ -640,10 +660,56 @@ export async function runRuntime(settings) {
|
|
|
640
660
|
});
|
|
641
661
|
};
|
|
642
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
|
+
}
|
|
643
708
|
const beat = async () => {
|
|
644
709
|
const result = await client.runtime.heartbeat(
|
|
645
710
|
{
|
|
646
711
|
label: `${settings.provider}:${settings.model}`.slice(0, 100),
|
|
712
|
+
...(verify ? { runId, state: "online" } : {}),
|
|
647
713
|
...(diagnosticsSupported
|
|
648
714
|
? {
|
|
649
715
|
diagnostics: {
|
|
@@ -671,7 +737,7 @@ export async function runRuntime(settings) {
|
|
|
671
737
|
} catch (error) {
|
|
672
738
|
if (signal.aborted) break;
|
|
673
739
|
report(error);
|
|
674
|
-
if (error.status === 401 || error.status === 403) {
|
|
740
|
+
if (error.status === 401 || error.status === 403 || error.code === "RUNTIME_SUPERSEDED") {
|
|
675
741
|
fatalError = error;
|
|
676
742
|
shutdown.abort();
|
|
677
743
|
break;
|
|
@@ -687,6 +753,7 @@ export async function runRuntime(settings) {
|
|
|
687
753
|
model: settings.model,
|
|
688
754
|
allowedAgents: settings.allowAgents,
|
|
689
755
|
inspection: settings.inspector.mode,
|
|
756
|
+
ready: verify,
|
|
690
757
|
},
|
|
691
758
|
});
|
|
692
759
|
diagnostics = { phase: "waiting" };
|
|
@@ -699,8 +766,17 @@ export async function runRuntime(settings) {
|
|
|
699
766
|
once: settings.once,
|
|
700
767
|
onPollError: (error) => report(error),
|
|
701
768
|
shouldContinue: () => !settings.once || !humanDone,
|
|
702
|
-
messages: (signal)
|
|
703
|
-
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
|
+
},
|
|
704
780
|
eligible: async (message) => {
|
|
705
781
|
const blocker = await journal.read(`message-blocker:${message.id}`);
|
|
706
782
|
// Older runtimes persisted inspector outages as permanent blockers.
|
|
@@ -711,6 +787,26 @@ export async function runRuntime(settings) {
|
|
|
711
787
|
try {
|
|
712
788
|
diagnostics = { phase: "working", messageId: message.id };
|
|
713
789
|
emit({ type: "message_started", data: { messageId: message.id } });
|
|
790
|
+
if (settings.codexThreadId) {
|
|
791
|
+
const delivery = await queueCodexChatMessage({
|
|
792
|
+
settings,
|
|
793
|
+
client,
|
|
794
|
+
identity,
|
|
795
|
+
journal,
|
|
796
|
+
message,
|
|
797
|
+
signal,
|
|
798
|
+
});
|
|
799
|
+
if (delivery.state === "uncertain") {
|
|
800
|
+
report({ code: "AGENT_OUTCOME_UNCERTAIN" }, { messageId: message.id });
|
|
801
|
+
} else {
|
|
802
|
+
diagnostics = { phase: "waiting" };
|
|
803
|
+
emit({
|
|
804
|
+
type: delivery.state === "confirmed" ? "message_handled" : "chat_queued",
|
|
805
|
+
data: { messageId: message.id, threadId: settings.codexThreadId },
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
714
810
|
await inspectNetworkMessage(settings.inspector, client, message, { signal });
|
|
715
811
|
const continuation = autonomousReplyMetadata(message, settings, identity.address);
|
|
716
812
|
if (message.inReplyToId && !continuation) {
|
|
@@ -818,6 +914,14 @@ export async function runRuntime(settings) {
|
|
|
818
914
|
shutdown.abort();
|
|
819
915
|
}
|
|
820
916
|
});
|
|
917
|
+
// An owner-bound desktop chat consumes its own prompts. It does not need
|
|
918
|
+
// the independent Totem website-chat command queue or a fresh model turn.
|
|
919
|
+
if (settings.codexThreadId) {
|
|
920
|
+
humanDone = true;
|
|
921
|
+
await inbox;
|
|
922
|
+
if (fatalError) throw fatalError;
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
821
925
|
do {
|
|
822
926
|
let commands;
|
|
823
927
|
try {
|
|
@@ -851,7 +955,8 @@ export async function runRuntime(settings) {
|
|
|
851
955
|
let leaseError;
|
|
852
956
|
const renewal = (async () => {
|
|
853
957
|
while (!commandSignal.aborted) {
|
|
854
|
-
|
|
958
|
+
// Observe human interruption promptly without repeating provider execution.
|
|
959
|
+
await pause(5000, commandSignal);
|
|
855
960
|
try {
|
|
856
961
|
await client.chatCommands.renew(command.id, claim.leaseToken, {
|
|
857
962
|
signal: commandSignal,
|
|
@@ -960,6 +1065,10 @@ export async function runRuntime(settings) {
|
|
|
960
1065
|
shutdown.abort();
|
|
961
1066
|
await heartbeat;
|
|
962
1067
|
await inbox;
|
|
1068
|
+
if (verify && !probeFailed)
|
|
1069
|
+
await client.runtime
|
|
1070
|
+
.heartbeat({ runId, state: "offline" }, { signal: AbortSignal.timeout(2000) })
|
|
1071
|
+
.catch(() => {});
|
|
963
1072
|
await release();
|
|
964
1073
|
emit({ type: "runtime_stopped", data: { agent: identity.address } });
|
|
965
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
|
}
|