@xtrape/capsule-agent-node 0.1.0-public-review.0 → 0.1.0-public-review.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/index.cjs +20 -6
- package/dist/index.d.cts +11 -12
- package/dist/index.d.ts +11 -12
- package/dist/index.js +20 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,15 @@ const agent = new CapsuleAgent({
|
|
|
46
46
|
backendUrl: process.env.OPSTAGE_BACKEND_URL ?? "http://localhost:8080",
|
|
47
47
|
registrationToken: process.env.OPSTAGE_REGISTRATION_TOKEN,
|
|
48
48
|
tokenStore: { file: "./data/agent-token.txt" },
|
|
49
|
+
// Optional. If omitted, the SDK derives agent identity from `service`.
|
|
50
|
+
// Provide it explicitly when one Agent owns multiple Capsule Services on
|
|
51
|
+
// the same host, or when you want a stable agent code that survives
|
|
52
|
+
// service renames.
|
|
53
|
+
agent: {
|
|
54
|
+
code: "hello-capsule-agent",
|
|
55
|
+
name: "Hello Capsule Agent",
|
|
56
|
+
runtime: "nodejs",
|
|
57
|
+
},
|
|
49
58
|
service: {
|
|
50
59
|
code: "hello-capsule",
|
|
51
60
|
name: "Hello Capsule",
|
|
@@ -300,6 +309,52 @@ Core methods:
|
|
|
300
309
|
Use matching minor versions across CE, Agent SDK, and Contracts during Public
|
|
301
310
|
Review and Public Preview. The wire protocol may still change before `v1.0`.
|
|
302
311
|
|
|
312
|
+
## Troubleshooting
|
|
313
|
+
|
|
314
|
+
### Registration fails with `401 / REGISTRATION_TOKEN_INVALID`
|
|
315
|
+
|
|
316
|
+
The token is single-use and short-lived.
|
|
317
|
+
|
|
318
|
+
- Check the token has not already been consumed by an earlier successful start
|
|
319
|
+
(registration tokens flip to `USED` on first use).
|
|
320
|
+
- Check the token has not expired. Operators set `expiresInSeconds` when
|
|
321
|
+
creating the token; the default in CE is short.
|
|
322
|
+
- Check the token has not been revoked from the Opstage console.
|
|
323
|
+
- Re-create a fresh token in the console and start the agent with it.
|
|
324
|
+
|
|
325
|
+
### Agent reports `ECONNREFUSED` / cannot reach backend
|
|
326
|
+
|
|
327
|
+
- Confirm `OPSTAGE_BACKEND_URL` resolves from inside the container or host
|
|
328
|
+
where the agent runs. The default of `http://localhost:8080` only works if
|
|
329
|
+
the agent runs on the same host as Opstage.
|
|
330
|
+
- If you sit behind a reverse proxy, point `backendUrl` at the proxy URL —
|
|
331
|
+
not the internal Opstage container address.
|
|
332
|
+
- Outbound HTTPS through corporate proxies: respect `HTTPS_PROXY` /
|
|
333
|
+
`NO_PROXY` env vars (`undici` honors them via `setGlobalDispatcher`).
|
|
334
|
+
|
|
335
|
+
### Agent token file is rejected on second start (`401 / UNAUTHORIZED`)
|
|
336
|
+
|
|
337
|
+
- File permissions: the agent process must be able to read
|
|
338
|
+
`tokenStore.file`. Run `chmod 600` (owner read/write) and ensure the
|
|
339
|
+
process owner matches.
|
|
340
|
+
- File contents: the SDK writes `<agentId>:<agentToken>` plaintext. If the
|
|
341
|
+
file exists but contains anything else, delete it and re-register with a
|
|
342
|
+
fresh registration token.
|
|
343
|
+
- Token revocation: an operator may have revoked the agent or the agent
|
|
344
|
+
token from the console. Inspect the agent's status in Opstage; if it shows
|
|
345
|
+
`REVOKED` or `DISABLED`, that's the cause.
|
|
346
|
+
|
|
347
|
+
### Heartbeats succeed but action results never arrive
|
|
348
|
+
|
|
349
|
+
- Confirm the action `name` registered with `agent.action({ name: "..." })`
|
|
350
|
+
matches the `actionName` the backend dispatches in the command. The SDK
|
|
351
|
+
reports `ACTION_HANDLER_NOT_FOUND` if they differ.
|
|
352
|
+
- Confirm `start()` is awaited and not called repeatedly — multiple
|
|
353
|
+
`CapsuleAgent` instances on the same host with the same agent code will
|
|
354
|
+
fight over heartbeats and command polls.
|
|
355
|
+
- Check `commandPollMs` / `commandPollIntervalSeconds` is not absurdly
|
|
356
|
+
large; default is 5s.
|
|
357
|
+
|
|
303
358
|
## Documentation
|
|
304
359
|
|
|
305
360
|
- Site: https://xtrape-com.github.io/xtrape-capsule-site/
|
package/dist/index.cjs
CHANGED
|
@@ -78,13 +78,17 @@ var AgentApiClient = class {
|
|
|
78
78
|
var import_promises = require("fs/promises");
|
|
79
79
|
var import_node_path = require("path");
|
|
80
80
|
var FileTokenStore = class {
|
|
81
|
-
constructor(filePath = "./data/agent-token.
|
|
81
|
+
constructor(filePath = "./data/agent-token.txt") {
|
|
82
82
|
this.filePath = filePath;
|
|
83
83
|
}
|
|
84
84
|
filePath;
|
|
85
85
|
async load() {
|
|
86
86
|
try {
|
|
87
|
-
const
|
|
87
|
+
const raw = await (0, import_promises.readFile)(this.filePath, "utf8");
|
|
88
|
+
const trimmed = raw.trim();
|
|
89
|
+
if (!trimmed) return null;
|
|
90
|
+
if (!trimmed.startsWith("{")) return trimmed;
|
|
91
|
+
const parsed = JSON.parse(trimmed);
|
|
88
92
|
if (typeof parsed.agentId === "string" && typeof parsed.agentToken === "string") return `${parsed.agentId}:${parsed.agentToken}`;
|
|
89
93
|
return typeof parsed.agentToken === "string" ? parsed.agentToken : null;
|
|
90
94
|
} catch (e) {
|
|
@@ -93,9 +97,8 @@ var FileTokenStore = class {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
async save(token) {
|
|
96
|
-
const [agentId, agentToken] = token.includes(":") ? token.split(":", 2) : [void 0, token];
|
|
97
100
|
await (0, import_promises.mkdir)((0, import_node_path.dirname)(this.filePath), { recursive: true });
|
|
98
|
-
await (0, import_promises.writeFile)(this.filePath,
|
|
101
|
+
await (0, import_promises.writeFile)(this.filePath, token, { mode: 384 });
|
|
99
102
|
try {
|
|
100
103
|
await (0, import_promises.chmod)(this.filePath, 384);
|
|
101
104
|
} catch {
|
|
@@ -107,11 +110,22 @@ var FileTokenStore = class {
|
|
|
107
110
|
};
|
|
108
111
|
|
|
109
112
|
// src/security/redaction.ts
|
|
113
|
+
var SENSITIVE_KEY = /token|secret|password|cookie|authorization|api[-_]?key\b|generatedKey|rawKey|plainTextKey/i;
|
|
114
|
+
var TOKEN_PATTERN = /opstage_[a-z]+_[A-Za-z0-9_-]+/g;
|
|
110
115
|
function redact(value) {
|
|
111
|
-
if (typeof value === "string")
|
|
116
|
+
if (typeof value === "string") {
|
|
117
|
+
return value.replace(TOKEN_PATTERN, (match) => {
|
|
118
|
+
const prefixEnd = match.lastIndexOf("_");
|
|
119
|
+
return `${match.slice(0, prefixEnd + 1)}[REDACTED]`;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
112
122
|
if (!value || typeof value !== "object") return value;
|
|
113
123
|
if (Array.isArray(value)) return value.map(redact);
|
|
114
|
-
return Object.fromEntries(
|
|
124
|
+
return Object.fromEntries(
|
|
125
|
+
Object.entries(value).map(
|
|
126
|
+
([k, v]) => SENSITIVE_KEY.test(k) ? [k, "[REDACTED]"] : [k, redact(v)]
|
|
127
|
+
)
|
|
128
|
+
);
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
// src/capsule-agent.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { HealthReportInput, ConfigItemInput, ActionDefinitionInput, ReportedService, RegisterAgentRequest, AgentHeartbeatRequest, ServiceReportRequest, ReportCommandResultRequest } from '@xtrape/capsule-contracts-node';
|
|
2
2
|
|
|
3
|
+
type RuntimeKind = "nodejs" | "java" | "python" | "go" | "other";
|
|
4
|
+
type ActionPrepareResult = {
|
|
5
|
+
action?: Record<string, unknown>;
|
|
6
|
+
inputSchema?: Record<string, unknown>;
|
|
7
|
+
initialPayload?: Record<string, unknown>;
|
|
8
|
+
currentState?: Record<string, unknown>;
|
|
9
|
+
};
|
|
3
10
|
type AgentMode = "embedded";
|
|
4
11
|
type ActionHandler = (payload: Record<string, unknown>) => Promise<{
|
|
5
12
|
success?: boolean;
|
|
@@ -7,15 +14,7 @@ type ActionHandler = (payload: Record<string, unknown>) => Promise<{
|
|
|
7
14
|
data?: Record<string, unknown>;
|
|
8
15
|
error?: Record<string, unknown>;
|
|
9
16
|
} | Record<string, unknown>>;
|
|
10
|
-
type ActionPrepareHandler = () => Promise<
|
|
11
|
-
action?: Record<string, unknown>;
|
|
12
|
-
initialPayload?: Record<string, unknown>;
|
|
13
|
-
currentState?: Record<string, unknown>;
|
|
14
|
-
} | Record<string, unknown>> | {
|
|
15
|
-
action?: Record<string, unknown>;
|
|
16
|
-
initialPayload?: Record<string, unknown>;
|
|
17
|
-
currentState?: Record<string, unknown>;
|
|
18
|
-
} | Record<string, unknown>;
|
|
17
|
+
type ActionPrepareHandler = () => Promise<ActionPrepareResult | Record<string, unknown>> | ActionPrepareResult | Record<string, unknown>;
|
|
19
18
|
interface CapsuleAgentOptions {
|
|
20
19
|
backendUrl: string;
|
|
21
20
|
registrationToken?: string;
|
|
@@ -25,14 +24,14 @@ interface CapsuleAgentOptions {
|
|
|
25
24
|
agent?: {
|
|
26
25
|
code: string;
|
|
27
26
|
name?: string;
|
|
28
|
-
runtime?:
|
|
27
|
+
runtime?: RuntimeKind;
|
|
29
28
|
};
|
|
30
29
|
service: {
|
|
31
30
|
code: string;
|
|
32
31
|
name: string;
|
|
33
32
|
description?: string;
|
|
34
33
|
version: string;
|
|
35
|
-
runtime:
|
|
34
|
+
runtime: RuntimeKind;
|
|
36
35
|
manifest?: Record<string, unknown>;
|
|
37
36
|
};
|
|
38
37
|
intervals?: {
|
|
@@ -136,4 +135,4 @@ declare class AgentApiError extends Error {
|
|
|
136
135
|
constructor(status: number, message: string, body?: unknown | undefined);
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
export { type ActionHandler, type ActionPrepareHandler, AgentApiClient, AgentApiError, type AgentMode, CapsuleAgent, type CapsuleAgentOptions, type ConfigProvider, FileTokenStore, type HealthProvider, type RegisteredAction, type ServiceSnapshot, type TokenStore };
|
|
138
|
+
export { type ActionHandler, type ActionPrepareHandler, type ActionPrepareResult, AgentApiClient, AgentApiError, type AgentMode, CapsuleAgent, type CapsuleAgentOptions, type ConfigProvider, FileTokenStore, type HealthProvider, type RegisteredAction, type RuntimeKind, type ServiceSnapshot, type TokenStore };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { HealthReportInput, ConfigItemInput, ActionDefinitionInput, ReportedService, RegisterAgentRequest, AgentHeartbeatRequest, ServiceReportRequest, ReportCommandResultRequest } from '@xtrape/capsule-contracts-node';
|
|
2
2
|
|
|
3
|
+
type RuntimeKind = "nodejs" | "java" | "python" | "go" | "other";
|
|
4
|
+
type ActionPrepareResult = {
|
|
5
|
+
action?: Record<string, unknown>;
|
|
6
|
+
inputSchema?: Record<string, unknown>;
|
|
7
|
+
initialPayload?: Record<string, unknown>;
|
|
8
|
+
currentState?: Record<string, unknown>;
|
|
9
|
+
};
|
|
3
10
|
type AgentMode = "embedded";
|
|
4
11
|
type ActionHandler = (payload: Record<string, unknown>) => Promise<{
|
|
5
12
|
success?: boolean;
|
|
@@ -7,15 +14,7 @@ type ActionHandler = (payload: Record<string, unknown>) => Promise<{
|
|
|
7
14
|
data?: Record<string, unknown>;
|
|
8
15
|
error?: Record<string, unknown>;
|
|
9
16
|
} | Record<string, unknown>>;
|
|
10
|
-
type ActionPrepareHandler = () => Promise<
|
|
11
|
-
action?: Record<string, unknown>;
|
|
12
|
-
initialPayload?: Record<string, unknown>;
|
|
13
|
-
currentState?: Record<string, unknown>;
|
|
14
|
-
} | Record<string, unknown>> | {
|
|
15
|
-
action?: Record<string, unknown>;
|
|
16
|
-
initialPayload?: Record<string, unknown>;
|
|
17
|
-
currentState?: Record<string, unknown>;
|
|
18
|
-
} | Record<string, unknown>;
|
|
17
|
+
type ActionPrepareHandler = () => Promise<ActionPrepareResult | Record<string, unknown>> | ActionPrepareResult | Record<string, unknown>;
|
|
19
18
|
interface CapsuleAgentOptions {
|
|
20
19
|
backendUrl: string;
|
|
21
20
|
registrationToken?: string;
|
|
@@ -25,14 +24,14 @@ interface CapsuleAgentOptions {
|
|
|
25
24
|
agent?: {
|
|
26
25
|
code: string;
|
|
27
26
|
name?: string;
|
|
28
|
-
runtime?:
|
|
27
|
+
runtime?: RuntimeKind;
|
|
29
28
|
};
|
|
30
29
|
service: {
|
|
31
30
|
code: string;
|
|
32
31
|
name: string;
|
|
33
32
|
description?: string;
|
|
34
33
|
version: string;
|
|
35
|
-
runtime:
|
|
34
|
+
runtime: RuntimeKind;
|
|
36
35
|
manifest?: Record<string, unknown>;
|
|
37
36
|
};
|
|
38
37
|
intervals?: {
|
|
@@ -136,4 +135,4 @@ declare class AgentApiError extends Error {
|
|
|
136
135
|
constructor(status: number, message: string, body?: unknown | undefined);
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
export { type ActionHandler, type ActionPrepareHandler, AgentApiClient, AgentApiError, type AgentMode, CapsuleAgent, type CapsuleAgentOptions, type ConfigProvider, FileTokenStore, type HealthProvider, type RegisteredAction, type ServiceSnapshot, type TokenStore };
|
|
138
|
+
export { type ActionHandler, type ActionPrepareHandler, type ActionPrepareResult, AgentApiClient, AgentApiError, type AgentMode, CapsuleAgent, type CapsuleAgentOptions, type ConfigProvider, FileTokenStore, type HealthProvider, type RegisteredAction, type RuntimeKind, type ServiceSnapshot, type TokenStore };
|
package/dist/index.js
CHANGED
|
@@ -49,13 +49,17 @@ var AgentApiClient = class {
|
|
|
49
49
|
import { mkdir, readFile, rm, writeFile, chmod } from "fs/promises";
|
|
50
50
|
import { dirname } from "path";
|
|
51
51
|
var FileTokenStore = class {
|
|
52
|
-
constructor(filePath = "./data/agent-token.
|
|
52
|
+
constructor(filePath = "./data/agent-token.txt") {
|
|
53
53
|
this.filePath = filePath;
|
|
54
54
|
}
|
|
55
55
|
filePath;
|
|
56
56
|
async load() {
|
|
57
57
|
try {
|
|
58
|
-
const
|
|
58
|
+
const raw = await readFile(this.filePath, "utf8");
|
|
59
|
+
const trimmed = raw.trim();
|
|
60
|
+
if (!trimmed) return null;
|
|
61
|
+
if (!trimmed.startsWith("{")) return trimmed;
|
|
62
|
+
const parsed = JSON.parse(trimmed);
|
|
59
63
|
if (typeof parsed.agentId === "string" && typeof parsed.agentToken === "string") return `${parsed.agentId}:${parsed.agentToken}`;
|
|
60
64
|
return typeof parsed.agentToken === "string" ? parsed.agentToken : null;
|
|
61
65
|
} catch (e) {
|
|
@@ -64,9 +68,8 @@ var FileTokenStore = class {
|
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
async save(token) {
|
|
67
|
-
const [agentId, agentToken] = token.includes(":") ? token.split(":", 2) : [void 0, token];
|
|
68
71
|
await mkdir(dirname(this.filePath), { recursive: true });
|
|
69
|
-
await writeFile(this.filePath,
|
|
72
|
+
await writeFile(this.filePath, token, { mode: 384 });
|
|
70
73
|
try {
|
|
71
74
|
await chmod(this.filePath, 384);
|
|
72
75
|
} catch {
|
|
@@ -78,11 +81,22 @@ var FileTokenStore = class {
|
|
|
78
81
|
};
|
|
79
82
|
|
|
80
83
|
// src/security/redaction.ts
|
|
84
|
+
var SENSITIVE_KEY = /token|secret|password|cookie|authorization|api[-_]?key\b|generatedKey|rawKey|plainTextKey/i;
|
|
85
|
+
var TOKEN_PATTERN = /opstage_[a-z]+_[A-Za-z0-9_-]+/g;
|
|
81
86
|
function redact(value) {
|
|
82
|
-
if (typeof value === "string")
|
|
87
|
+
if (typeof value === "string") {
|
|
88
|
+
return value.replace(TOKEN_PATTERN, (match) => {
|
|
89
|
+
const prefixEnd = match.lastIndexOf("_");
|
|
90
|
+
return `${match.slice(0, prefixEnd + 1)}[REDACTED]`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
83
93
|
if (!value || typeof value !== "object") return value;
|
|
84
94
|
if (Array.isArray(value)) return value.map(redact);
|
|
85
|
-
return Object.fromEntries(
|
|
95
|
+
return Object.fromEntries(
|
|
96
|
+
Object.entries(value).map(
|
|
97
|
+
([k, v]) => SENSITIVE_KEY.test(k) ? [k, "[REDACTED]"] : [k, redact(v)]
|
|
98
|
+
)
|
|
99
|
+
);
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
// src/capsule-agent.ts
|
package/package.json
CHANGED