@factory/droid-sdk 0.9.1 → 0.11.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/README.md +53 -0
- package/dist/api.d.mts +15058 -0
- package/dist/api.d.ts +15058 -0
- package/dist/api.js +253 -0
- package/dist/api.js.map +1 -0
- package/dist/api.mjs +253 -0
- package/dist/api.mjs.map +1 -0
- package/dist/chunk-7ZD7MMNA.mjs +2268 -0
- package/dist/chunk-7ZD7MMNA.mjs.map +1 -0
- package/dist/{chunk-YSKO6SPT.js → chunk-KAAWCHO4.js} +32033 -17088
- package/dist/chunk-KAAWCHO4.js.map +1 -0
- package/dist/chunk-NWFX4MPJ.mjs +41630 -0
- package/dist/chunk-NWFX4MPJ.mjs.map +1 -0
- package/dist/chunk-ZQ7TS73S.js +2268 -0
- package/dist/chunk-ZQ7TS73S.js.map +1 -0
- package/dist/{index-C06W9tbN.d.mts → index-Cf2WtJqJ.d.mts} +134169 -84330
- package/dist/{index-C06W9tbN.d.ts → index-Cf2WtJqJ.d.ts} +134169 -84330
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +91 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +99 -10
- package/dist/node.d.mts +223 -43
- package/dist/node.d.ts +223 -43
- package/dist/node.js +2033 -776
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +1756 -499
- package/dist/node.mjs.map +1 -1
- package/examples/browser/daemon-lifecycle.ts +4 -6
- package/examples/node/custom-model-editing-disposable.sh +88 -0
- package/examples/node/custom-model-editing-disposable.ts +171 -0
- package/examples/node/custom-model-editing.ts +55 -0
- package/examples/node/hook-execution.ts +3 -2
- package/examples/node/rest-api.ts +55 -0
- package/examples/node/sdk-mcp-tool.ts +5 -3
- package/examples/node/start-workstream.ts +76 -0
- package/examples/node/tool-controls.ts +7 -5
- package/package.json +14 -3
- package/dist/chunk-TWSTJMV7.mjs +0 -26685
- package/dist/chunk-TWSTJMV7.mjs.map +0 -1
- package/dist/chunk-YSKO6SPT.js.map +0 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review the complete custom-model edit flow against the isolated daemon
|
|
3
|
+
* started by custom-model-editing-disposable.sh. No provider call is made.
|
|
4
|
+
*
|
|
5
|
+
* From the repository root:
|
|
6
|
+
* bash packages/droid-sdk-ext/examples/node/custom-model-editing-disposable.sh
|
|
7
|
+
*/
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { connectToDaemon } from '@factory/droid-sdk';
|
|
14
|
+
|
|
15
|
+
const home = process.env.BYOK_REPRO_HOME;
|
|
16
|
+
if (
|
|
17
|
+
!home ||
|
|
18
|
+
home !== process.env.HOME ||
|
|
19
|
+
home !== process.env.FACTORY_HOME_OVERRIDE ||
|
|
20
|
+
process.env.FACTORY_AIRGAP_ENABLED !== 'true' ||
|
|
21
|
+
!existsSync(join(home, '.byok-review-scratch'))
|
|
22
|
+
) {
|
|
23
|
+
throw new Error('Run this example through its disposable daemon script.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const url = process.env.DROID_DAEMON_URL;
|
|
27
|
+
const apiKey = process.env.FACTORY_API_KEY;
|
|
28
|
+
if (!url?.startsWith('ws://127.0.0.1:') || !apiKey) {
|
|
29
|
+
throw new Error('Expected the isolated loopback daemon and test auth key.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const settingsPath = join(home, '.factory-dev', 'settings.json');
|
|
33
|
+
if (existsSync(settingsPath)) {
|
|
34
|
+
throw new Error('Refusing to edit a pre-existing settings file.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const model = 'review-synthetic-model';
|
|
38
|
+
const byokKey = `review-${randomBytes(16).toString('hex')}`;
|
|
39
|
+
|
|
40
|
+
function assertStored(displayName: string): void {
|
|
41
|
+
const raw: unknown = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
42
|
+
assert.ok(raw && typeof raw === 'object' && 'customModels' in raw);
|
|
43
|
+
const models = raw.customModels;
|
|
44
|
+
assert.ok(Array.isArray(models) && models.length === 1);
|
|
45
|
+
const stored: unknown = models[0];
|
|
46
|
+
assert.ok(stored && typeof stored === 'object');
|
|
47
|
+
assert.ok(
|
|
48
|
+
'apiKey' in stored && stored.apiKey === byokKey,
|
|
49
|
+
'The synthetic BYOK key was not retained on disk.'
|
|
50
|
+
);
|
|
51
|
+
assert.ok(
|
|
52
|
+
'displayName' in stored && stored.displayName === displayName,
|
|
53
|
+
'The model name did not persist.'
|
|
54
|
+
);
|
|
55
|
+
assert.ok(
|
|
56
|
+
'useInRouter' in stored && stored.useInRouter === true,
|
|
57
|
+
'The JSON-authored router opt-in did not persist.'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function assertRpcError(error: unknown, code: number, reason?: string): void {
|
|
62
|
+
if (!(error instanceof Error && 'error' in error)) {
|
|
63
|
+
throw new Error('Expected a structured daemon RPC error.');
|
|
64
|
+
}
|
|
65
|
+
const rpcError = error.error;
|
|
66
|
+
if (!(rpcError && typeof rpcError === 'object' && 'code' in rpcError)) {
|
|
67
|
+
throw new Error('Expected a structured daemon RPC error.');
|
|
68
|
+
}
|
|
69
|
+
assert.ok(rpcError.code === code, 'Unexpected daemon RPC error code.');
|
|
70
|
+
if (reason !== undefined) {
|
|
71
|
+
assert.ok('data' in rpcError, 'Expected a structured conflict reason.');
|
|
72
|
+
const data = rpcError.data;
|
|
73
|
+
assert.ok(
|
|
74
|
+
data &&
|
|
75
|
+
typeof data === 'object' &&
|
|
76
|
+
'reason' in data &&
|
|
77
|
+
data.reason === reason,
|
|
78
|
+
'Unexpected daemon conflict reason.'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const droid = await connectToDaemon({ url, auth: { apiKey } });
|
|
84
|
+
try {
|
|
85
|
+
const empty = await droid.customModels.listWithRevision();
|
|
86
|
+
assert.ok(empty.revision && empty.models.length === 0);
|
|
87
|
+
|
|
88
|
+
await droid.customModels.upsert({
|
|
89
|
+
model,
|
|
90
|
+
provider: 'anthropic',
|
|
91
|
+
expectedRevision: empty.revision,
|
|
92
|
+
configuration: {
|
|
93
|
+
model,
|
|
94
|
+
provider: 'anthropic',
|
|
95
|
+
baseUrl: 'https://example.invalid/anthropic',
|
|
96
|
+
displayName: 'Created',
|
|
97
|
+
apiKey: byokKey,
|
|
98
|
+
useInRouter: true,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
assertStored('Created');
|
|
102
|
+
console.log('PASS: complete create persisted in the isolated settings file');
|
|
103
|
+
|
|
104
|
+
const opening = await droid.customModels.listWithRevision();
|
|
105
|
+
const row = opening.models[0];
|
|
106
|
+
assert.ok(opening.revision && row?.editableConfig);
|
|
107
|
+
assert.ok(row.isValid && row.hasApiKey);
|
|
108
|
+
assert.ok(!Object.hasOwn(row.editableConfig, 'apiKey'));
|
|
109
|
+
assert.ok(!JSON.stringify(opening).includes(byokKey));
|
|
110
|
+
assert.deepEqual(row.redactedSecrets, [['apiKey']]);
|
|
111
|
+
assert.ok(row.editableConfig.useInRouter === true);
|
|
112
|
+
|
|
113
|
+
await droid.customModels.upsert({
|
|
114
|
+
rawIndex: row.rawIndex,
|
|
115
|
+
expectedModel: row.model,
|
|
116
|
+
model: row.model,
|
|
117
|
+
provider: row.provider,
|
|
118
|
+
expectedRevision: opening.revision,
|
|
119
|
+
configuration: { ...row.editableConfig, displayName: 'Edited' },
|
|
120
|
+
preservedSecrets: row.redactedSecrets,
|
|
121
|
+
});
|
|
122
|
+
assertStored('Edited');
|
|
123
|
+
console.log('PASS: redacted edit retained the synthetic credential');
|
|
124
|
+
|
|
125
|
+
const current = await droid.customModels.listWithRevision();
|
|
126
|
+
assert.ok(current.revision && current.revision !== opening.revision);
|
|
127
|
+
assert.ok(current.models[0]?.editableConfig);
|
|
128
|
+
|
|
129
|
+
let staleRejected = false;
|
|
130
|
+
try {
|
|
131
|
+
await droid.customModels.upsert({
|
|
132
|
+
rawIndex: row.rawIndex,
|
|
133
|
+
expectedModel: row.model,
|
|
134
|
+
model: row.model,
|
|
135
|
+
provider: row.provider,
|
|
136
|
+
expectedRevision: opening.revision,
|
|
137
|
+
configuration: { ...row.editableConfig, displayName: 'Stale overwrite' },
|
|
138
|
+
preservedSecrets: row.redactedSecrets,
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
assertRpcError(error, -32006, 'stale_revision');
|
|
142
|
+
staleRejected = true;
|
|
143
|
+
}
|
|
144
|
+
assert.ok(staleRejected, 'The stale write unexpectedly succeeded.');
|
|
145
|
+
assertStored('Edited');
|
|
146
|
+
console.log('PASS: stale edit returned CONFLICT and changed nothing');
|
|
147
|
+
|
|
148
|
+
let invalidRejected = false;
|
|
149
|
+
try {
|
|
150
|
+
await droid.customModels.upsert({
|
|
151
|
+
rawIndex: row.rawIndex,
|
|
152
|
+
expectedModel: row.model,
|
|
153
|
+
model: row.model,
|
|
154
|
+
provider: row.provider,
|
|
155
|
+
expectedRevision: current.revision,
|
|
156
|
+
configuration: {
|
|
157
|
+
...current.models[0].editableConfig,
|
|
158
|
+
unexpectedField: 'not supported',
|
|
159
|
+
},
|
|
160
|
+
preservedSecrets: current.models[0].redactedSecrets,
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
assertRpcError(error, -32602);
|
|
164
|
+
invalidRejected = true;
|
|
165
|
+
}
|
|
166
|
+
assert.ok(invalidRejected, 'The invalid edit unexpectedly succeeded.');
|
|
167
|
+
assertStored('Edited');
|
|
168
|
+
console.log('PASS: invalid complete edit returned INVALID_PARAMS');
|
|
169
|
+
} finally {
|
|
170
|
+
droid.disconnect();
|
|
171
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a custom-model edit snapshot, or explicitly rename one model.
|
|
3
|
+
*
|
|
4
|
+
* Set DROID_DAEMON_URL and FACTORY_API_KEY for an existing daemon.
|
|
5
|
+
* Read-only: npx tsx examples/node/custom-model-editing.ts
|
|
6
|
+
* Rename: npx tsx examples/node/custom-model-editing.ts <raw-index> <display-name>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { connectToDaemon } from '@factory/droid-sdk';
|
|
10
|
+
|
|
11
|
+
const url = process.env.DROID_DAEMON_URL;
|
|
12
|
+
const apiKey = process.env.FACTORY_API_KEY;
|
|
13
|
+
if (!url || !apiKey) {
|
|
14
|
+
throw new Error('Set DROID_DAEMON_URL and FACTORY_API_KEY.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [rawIndexText, displayName] = process.argv.slice(2);
|
|
18
|
+
const rawIndex = rawIndexText === undefined ? undefined : Number(rawIndexText);
|
|
19
|
+
if (
|
|
20
|
+
rawIndex !== undefined &&
|
|
21
|
+
(!Number.isInteger(rawIndex) || rawIndex < 0 || displayName === undefined)
|
|
22
|
+
) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'Pass a non-negative raw index and a display name to rename.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const droid = await connectToDaemon({ url, auth: { apiKey } });
|
|
29
|
+
try {
|
|
30
|
+
// list() remains the array-only display API. Editors need this snapshot.
|
|
31
|
+
const snapshot = await droid.customModels.listWithRevision();
|
|
32
|
+
console.log(`Found ${snapshot.models.length} personal custom models.`);
|
|
33
|
+
if (rawIndex !== undefined) {
|
|
34
|
+
const model = snapshot.models.find((entry) => entry.rawIndex === rawIndex);
|
|
35
|
+
if (!snapshot.revision || !model?.editableConfig) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
'Model is missing or the daemon does not support full editing.'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
await droid.customModels.upsert({
|
|
41
|
+
rawIndex: model.rawIndex,
|
|
42
|
+
expectedModel: model.model,
|
|
43
|
+
expectedRevision: snapshot.revision,
|
|
44
|
+
model: model.model,
|
|
45
|
+
provider: model.provider,
|
|
46
|
+
configuration: { ...model.editableConfig, displayName },
|
|
47
|
+
preservedSecrets: model.redactedSecrets,
|
|
48
|
+
});
|
|
49
|
+
console.log(
|
|
50
|
+
'Model renamed. Reload the snapshot before the next edit or delete.'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
droid.disconnect();
|
|
55
|
+
}
|
|
@@ -45,10 +45,11 @@ try {
|
|
|
45
45
|
const session = await createSession({
|
|
46
46
|
cwd: projectDir,
|
|
47
47
|
permissionHandler({ toolUses, options }) {
|
|
48
|
+
const [toolUse] = toolUses;
|
|
48
49
|
const approvesExpectedCommand =
|
|
49
50
|
toolUses.length === 1 &&
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
toolUse?.details.type === ToolConfirmationType.Execute &&
|
|
52
|
+
toolUse.details.fullCommand === expectedCommand;
|
|
52
53
|
const desired = approvesExpectedCommand
|
|
53
54
|
? ToolConfirmationOutcome.ProceedOnce
|
|
54
55
|
: ToolConfirmationOutcome.Cancel;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory REST API example.
|
|
3
|
+
*
|
|
4
|
+
* Lists the Computers and remote sessions an API key can see, using the
|
|
5
|
+
* client generated from the public Factory API spec.
|
|
6
|
+
*
|
|
7
|
+
* These operations take `{ client }` from `createFactoryClient({ apiKey, baseUrl })`
|
|
8
|
+
* and resolve `{ data, error }`. The root `@factory/droid-sdk` REST helpers
|
|
9
|
+
* take `{ apiKey, baseUrl }` and resolve the response body. Several names
|
|
10
|
+
* exist on both with different contracts, so a file that imports both should
|
|
11
|
+
* alias the shared names or import this entry under a namespace. A root
|
|
12
|
+
* options object cannot be passed here: generated operations require a
|
|
13
|
+
* `client` created with `createFactoryClient`.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* FACTORY_API_KEY=... npx tsx examples/node/rest-api.ts [baseUrl]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
createFactoryClient,
|
|
21
|
+
listComputers,
|
|
22
|
+
listSessions,
|
|
23
|
+
} from '@factory/droid-sdk/api';
|
|
24
|
+
|
|
25
|
+
const apiKey = process.env.FACTORY_API_KEY;
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
throw new Error('Set FACTORY_API_KEY.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const client = createFactoryClient({
|
|
31
|
+
apiKey,
|
|
32
|
+
baseUrl: process.argv[2],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const computers = await listComputers({ client });
|
|
36
|
+
if (computers.error !== undefined) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Listing Computers failed: ${JSON.stringify(computers.error)}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
console.log(`Computers (${computers.data.computers.length}):`);
|
|
42
|
+
for (const computer of computers.data.computers) {
|
|
43
|
+
console.log(` ${computer.id} ${computer.name} ${computer.status ?? ''}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sessions = await listSessions({ client, query: { limit: 5 } });
|
|
47
|
+
if (sessions.error !== undefined) {
|
|
48
|
+
throw new Error(`Listing sessions failed: ${JSON.stringify(sessions.error)}`);
|
|
49
|
+
}
|
|
50
|
+
console.log(`Recent sessions (${sessions.data.sessions.length}):`);
|
|
51
|
+
for (const session of sessions.data.sessions) {
|
|
52
|
+
console.log(
|
|
53
|
+
` ${session.sessionId} ${session.status} ${session.messageCount} messages`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SDK MCP tool example.
|
|
3
3
|
*
|
|
4
|
-
* Defines an in-process tool with `createSdkMcpServer` + `tool` and
|
|
5
|
-
* to Droid, then asks Droid to call it.
|
|
4
|
+
* Defines an in-process read-only tool with `createSdkMcpServer` + `tool` and
|
|
5
|
+
* exposes it to Droid, then asks Droid to call it.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* npx tsx examples/node/sdk-mcp-tool.ts
|
|
@@ -25,7 +25,9 @@ const sdkTools = createSdkMcpServer({
|
|
|
25
25
|
'favorite_number',
|
|
26
26
|
'Returns a favorite number for a person',
|
|
27
27
|
{ name: z.string() },
|
|
28
|
-
({ name }) => `${name}'s favorite number is 42
|
|
28
|
+
({ name }) => `${name}'s favorite number is 42.`,
|
|
29
|
+
// Read-only tools run without a permission prompt.
|
|
30
|
+
{ annotations: { readOnlyHint: true } }
|
|
29
31
|
),
|
|
30
32
|
],
|
|
31
33
|
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start a workstream on a running daemon and leave it running.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator plans the goal and runs agents in the daemon, which keeps
|
|
5
|
+
* the workstream going after this script exits. Without an `askUserHandler`,
|
|
6
|
+
* any question the orchestrator asks is declined (see ask-user-elicitation.ts).
|
|
7
|
+
* An agent's questions and permission prompts are answered only by a client
|
|
8
|
+
* that attaches that agent with handlers (`droid.sessions.resume(agentId, ...)`).
|
|
9
|
+
*
|
|
10
|
+
* Set DROID_DAEMON_URL and FACTORY_API_KEY for an existing daemon whose account
|
|
11
|
+
* has access to workstreams. Set WORKSTREAM_COMPUTER_ID to let agents also run
|
|
12
|
+
* on that computer instead of only on this daemon's machine.
|
|
13
|
+
* Usage: npx tsx examples/node/start-workstream.ts "<goal>" [cwd]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
AutonomyLevel,
|
|
18
|
+
DroidMessageType,
|
|
19
|
+
ReasoningEffort,
|
|
20
|
+
WorkstreamMachineTargetKind,
|
|
21
|
+
connectToDaemon,
|
|
22
|
+
} from '@factory/droid-sdk';
|
|
23
|
+
|
|
24
|
+
const url = process.env.DROID_DAEMON_URL;
|
|
25
|
+
const apiKey = process.env.FACTORY_API_KEY;
|
|
26
|
+
const computerId = process.env.WORKSTREAM_COMPUTER_ID;
|
|
27
|
+
if (!url || !apiKey) {
|
|
28
|
+
throw new Error('Set DROID_DAEMON_URL and FACTORY_API_KEY.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [goal, cwd = process.cwd()] = process.argv.slice(2);
|
|
32
|
+
if (!goal) {
|
|
33
|
+
throw new Error('Pass the workstream goal as the first argument.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const droid = await connectToDaemon({ url, auth: { apiKey } });
|
|
37
|
+
try {
|
|
38
|
+
// Agents on another machine reach this daemon through its relay.
|
|
39
|
+
if (computerId) await droid.relay.start();
|
|
40
|
+
const orchestrator = await droid.unstable.workstreams.start({
|
|
41
|
+
cwd,
|
|
42
|
+
title: goal.slice(0, 80),
|
|
43
|
+
// Agents inherit the orchestrator's autonomy; below High they stop to ask.
|
|
44
|
+
autonomyLevel: AutonomyLevel.High,
|
|
45
|
+
agentReasoningEffort: ReasoningEffort.High,
|
|
46
|
+
...(computerId
|
|
47
|
+
? {
|
|
48
|
+
machines: [
|
|
49
|
+
{
|
|
50
|
+
label: 'remote',
|
|
51
|
+
target: {
|
|
52
|
+
kind: WorkstreamMachineTargetKind.Computer,
|
|
53
|
+
computerId,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
defaultMachineLabel: 'remote',
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
});
|
|
61
|
+
console.log(`Workstream ${orchestrator.id} started.`);
|
|
62
|
+
|
|
63
|
+
for await (const msg of orchestrator.stream(goal)) {
|
|
64
|
+
if (msg.type === DroidMessageType.Assistant) {
|
|
65
|
+
process.stdout.write(msg.text);
|
|
66
|
+
} else if (msg.type === DroidMessageType.Result && !msg.success) {
|
|
67
|
+
throw new Error(msg.error?.message ?? `Turn ended: ${msg.subtype}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Detach, not close: closing would end the workstream in the daemon.
|
|
72
|
+
await orchestrator.detach();
|
|
73
|
+
console.log(`\nWorkstream ${orchestrator.id} keeps running in the daemon.`);
|
|
74
|
+
} finally {
|
|
75
|
+
droid.disconnect();
|
|
76
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool controls example.
|
|
3
3
|
*
|
|
4
|
-
* Disables tools at session creation, then inspects availability.
|
|
4
|
+
* Disables tools at session creation, then inspects availability and schemas.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* npx tsx examples/node/tool-controls.ts
|
|
@@ -14,12 +14,14 @@ const session = await createSession({
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
|
-
const tools = await session.listTools(
|
|
17
|
+
const tools = await session.listTools({
|
|
18
|
+
includeSchemas: true,
|
|
19
|
+
toolIds: ['Read', 'Grep', 'Execute'],
|
|
20
|
+
});
|
|
18
21
|
|
|
19
22
|
for (const tool of tools) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
+
console.log(`${tool.id}: ${String(tool.allowed)}`);
|
|
24
|
+
console.log(JSON.stringify(tool.schemas, null, 2));
|
|
23
25
|
}
|
|
24
26
|
} finally {
|
|
25
27
|
await session.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factory/droid-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript SDK for the Factory Droid CLI",
|
|
6
6
|
"keywords": [
|
|
@@ -35,6 +35,16 @@
|
|
|
35
35
|
"types": "./dist/node.d.ts",
|
|
36
36
|
"default": "./dist/node.js"
|
|
37
37
|
}
|
|
38
|
+
},
|
|
39
|
+
"./api": {
|
|
40
|
+
"import": {
|
|
41
|
+
"types": "./dist/api.d.mts",
|
|
42
|
+
"default": "./dist/api.mjs"
|
|
43
|
+
},
|
|
44
|
+
"require": {
|
|
45
|
+
"types": "./dist/api.d.ts",
|
|
46
|
+
"default": "./dist/api.js"
|
|
47
|
+
}
|
|
38
48
|
}
|
|
39
49
|
},
|
|
40
50
|
"browser": {
|
|
@@ -54,12 +64,13 @@
|
|
|
54
64
|
"node": ">=18.0.0"
|
|
55
65
|
},
|
|
56
66
|
"dependencies": {
|
|
57
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
58
68
|
"@opentelemetry/api": "^1.9.0",
|
|
59
69
|
"eventemitter3": "^5.0.1",
|
|
60
70
|
"fast-deep-equal": "^3.1.3",
|
|
61
71
|
"isows": "^1.0.7",
|
|
62
|
-
"
|
|
72
|
+
"libsodium-wrappers-sumo": "^0.8.4",
|
|
73
|
+
"uuid": "^11.1.1",
|
|
63
74
|
"ws": "^8.21.0",
|
|
64
75
|
"zod": "^3.24.0"
|
|
65
76
|
},
|