@devicelink/dlink 0.2.1 → 0.3.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 +24 -37
- package/index.js +115 -27
- package/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @devicelink/dlink
|
|
2
2
|
|
|
3
|
-
DeviceLink
|
|
4
|
-
|
|
5
|
-
Share files from your machine with AI assistants (ChatGPT, Claude, etc.) through the Model Context Protocol (MCP). Your files never leave your device — they're streamed in real-time with your explicit permission.
|
|
3
|
+
DeviceLink lets you share folders from your computer with AI assistants (like ChatGPT) safely. Nothing is uploaded to the cloud to sit on a server: files stay on your machine until you allow access, and you stay in control.
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
@@ -10,58 +8,47 @@ Share files from your machine with AI assistants (ChatGPT, Claude, etc.) through
|
|
|
10
8
|
npm install -g @devicelink/dlink
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
## Quick
|
|
11
|
+
## Quick start
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
# Sign in with your DeviceLink account
|
|
17
15
|
dlink signin
|
|
18
16
|
|
|
19
|
-
#
|
|
17
|
+
# Pick a folder to share
|
|
20
18
|
dlink share /path/to/project
|
|
21
19
|
|
|
22
|
-
#
|
|
20
|
+
# You're set: your AI can use that folder when you connect it through DeviceLink
|
|
23
21
|
```
|
|
24
22
|
|
|
25
23
|
## Commands
|
|
26
24
|
|
|
27
|
-
| Command |
|
|
28
|
-
|
|
29
|
-
| `dlink signin` |
|
|
30
|
-
| `dlink signout` | Sign out
|
|
31
|
-
| `dlink share <path>` | Share a
|
|
32
|
-
| `dlink status` |
|
|
33
|
-
| `dlink debug on/off` | Toggle debug logging |
|
|
34
|
-
|
|
35
|
-
## How It Works
|
|
36
|
-
|
|
37
|
-
1. **Sign in** — authenticate via Google OAuth using a device code flow
|
|
38
|
-
2. **Share directories** — choose which folders to make available
|
|
39
|
-
3. **Connect** — the agent maintains a secure WebSocket connection to the DeviceLink bridge
|
|
40
|
-
4. **Use** — AI assistants call tools (list files, read files, search, grep) through MCP
|
|
41
|
-
5. **Control** — you see every tool call in real-time and can disconnect anytime
|
|
42
|
-
|
|
43
|
-
## Security
|
|
25
|
+
| Command | What it does |
|
|
26
|
+
|---------|----------------|
|
|
27
|
+
| `dlink signin` | Sign in to DeviceLink |
|
|
28
|
+
| `dlink signout` | Sign out on this computer |
|
|
29
|
+
| `dlink share <path>` | Share a folder with your AI |
|
|
30
|
+
| `dlink status` | See whether you're connected |
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
- **Ed25519 proof-of-possession** — device identity is cryptographically verified
|
|
47
|
-
- **Encrypted credential storage** — AES-256-GCM with machine-bound keys
|
|
48
|
-
- **Shared directory boundaries** — path traversal protection, symlink escape prevention
|
|
49
|
-
- **Short-lived tokens** — access tokens expire in 30 seconds, auto-refreshed
|
|
32
|
+
## How it works
|
|
50
33
|
|
|
51
|
-
|
|
34
|
+
1. **Sign in** with your account (you'll use your browser when asked).
|
|
35
|
+
2. **Pick folders** you want the AI to be able to read.
|
|
36
|
+
3. **Stay connected** so requests can reach your computer securely.
|
|
37
|
+
4. **Use your AI** as usual: it can list and read files in the folders you shared.
|
|
38
|
+
5. **Stop anytime**: sign out or disconnect if you want access to end.
|
|
52
39
|
|
|
53
|
-
|
|
40
|
+
## Security & privacy
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
42
|
+
- Your files are not kept on our servers for browsing; access happens when you're connected and allow it.
|
|
43
|
+
- Your device is checked so only your account can use your shared folders.
|
|
44
|
+
- Sign-in details are stored encrypted on this computer.
|
|
45
|
+
- Shared folders are limited to what you picked, so the AI can't wander the rest of your disk.
|
|
46
|
+
- Access is short-lived and refreshed automatically so old keys don't stick around.
|
|
60
47
|
|
|
61
48
|
## Requirements
|
|
62
49
|
|
|
63
|
-
- Node.js 20 or
|
|
64
|
-
- Internet
|
|
50
|
+
- [Node.js](https://nodejs.org/) version 20 or newer
|
|
51
|
+
- Internet access to sign in and stay connected
|
|
65
52
|
|
|
66
53
|
## License
|
|
67
54
|
|
package/index.js
CHANGED
|
@@ -692,7 +692,8 @@ var ConfigLoader = class {
|
|
|
692
692
|
} catch {
|
|
693
693
|
}
|
|
694
694
|
}
|
|
695
|
-
|
|
695
|
+
const cwd = process.cwd();
|
|
696
|
+
return [this.createDirectoryFromPath(cwd)];
|
|
696
697
|
}
|
|
697
698
|
getDevice() {
|
|
698
699
|
const deviceId = this.getDeviceId();
|
|
@@ -961,6 +962,13 @@ var Engine2 = class {
|
|
|
961
962
|
getBus() {
|
|
962
963
|
return this.bus;
|
|
963
964
|
}
|
|
965
|
+
hasRunningJobs() {
|
|
966
|
+
for (const [, count] of this.laneActive) {
|
|
967
|
+
if (count > 0)
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
964
972
|
register(jobType) {
|
|
965
973
|
const name = jobType.options.name;
|
|
966
974
|
if (this.registry.has(name)) {
|
|
@@ -1587,7 +1595,7 @@ var LoadConfigJob = class extends JobType {
|
|
|
1587
1595
|
);
|
|
1588
1596
|
}
|
|
1589
1597
|
async run(input, ctx) {
|
|
1590
|
-
ctx.notifier.info("Loading configuration...");
|
|
1598
|
+
ctx.notifier.info("Loading configuration...", { scope: "system" /* System */ });
|
|
1591
1599
|
const configLoader = ctx.engine.configurationContext.loader;
|
|
1592
1600
|
const refreshRes = await ctx.engine.secrets.get("devicePrivateKey");
|
|
1593
1601
|
configLoader.setPrivateKeyCache(refreshRes.ok && refreshRes.value ? refreshRes.value : void 0);
|
|
@@ -1610,13 +1618,14 @@ var LoadConfigJob = class extends JobType {
|
|
|
1610
1618
|
return new LoadConfigOutput(true, config);
|
|
1611
1619
|
}
|
|
1612
1620
|
updateUi(config, ctx) {
|
|
1613
|
-
|
|
1614
|
-
ctx.notifier.info(`
|
|
1615
|
-
ctx.notifier.info(`
|
|
1616
|
-
ctx.notifier.info(`
|
|
1617
|
-
ctx.notifier.info(`Device
|
|
1618
|
-
ctx.notifier.info(`Device
|
|
1619
|
-
ctx.notifier.info(`Device
|
|
1621
|
+
const sys = { scope: "system" /* System */ };
|
|
1622
|
+
ctx.notifier.info(`Control base URL: ${config.controlBaseUrl}`, sys);
|
|
1623
|
+
ctx.notifier.info(`Bridge WebSocket URL: ${config.bridgeWsUrl}`, sys);
|
|
1624
|
+
ctx.notifier.info(`Directories: ${config.directories.map((directory) => directory.path).join(", ")}`, sys);
|
|
1625
|
+
ctx.notifier.info(`Device: ${config.device?.id}`, sys);
|
|
1626
|
+
ctx.notifier.info(`Device name: ${config.device?.name}`, sys);
|
|
1627
|
+
ctx.notifier.info(`Device tag: ${config.device?.tag}`, sys);
|
|
1628
|
+
ctx.notifier.info(`Device keys public: ${config.device?.keys?.public ? "***" : "(none)"}`, sys);
|
|
1620
1629
|
}
|
|
1621
1630
|
};
|
|
1622
1631
|
var loadConfigJob = new LoadConfigJob();
|
|
@@ -3311,6 +3320,9 @@ var ConsoleOrchestrator = class {
|
|
|
3311
3320
|
ctx.bus.emit("runtime:debug:changed", { enabled: false });
|
|
3312
3321
|
ctx.notifier.success("Debug mode disabled");
|
|
3313
3322
|
break;
|
|
3323
|
+
case "replace" /* Replace */:
|
|
3324
|
+
ctx.bus.emit("auth:session_conflict:resolve", { choice: "USE_NEW" });
|
|
3325
|
+
break;
|
|
3314
3326
|
default:
|
|
3315
3327
|
ctx.notifier.warn(`Command not implemented: ${input.kind}`);
|
|
3316
3328
|
break;
|
|
@@ -3360,6 +3372,9 @@ var ConsoleOrchestrator = class {
|
|
|
3360
3372
|
if (cmd === "doctor") {
|
|
3361
3373
|
return { handled: true, kind: "doctor" /* Doctor */ };
|
|
3362
3374
|
}
|
|
3375
|
+
if (cmd === "replace" || cmd === "yes" || cmd === "y") {
|
|
3376
|
+
return { handled: true, kind: "replace" /* Replace */ };
|
|
3377
|
+
}
|
|
3363
3378
|
}
|
|
3364
3379
|
return { handled: false };
|
|
3365
3380
|
});
|
|
@@ -3809,7 +3824,6 @@ var WebSocketOrchestrator = class {
|
|
|
3809
3824
|
};
|
|
3810
3825
|
return {
|
|
3811
3826
|
"config:cache:validated": async (_, ctx) => initSocket("config:cache:validated", ctx),
|
|
3812
|
-
"auth:signin:completed": async (_, ctx) => initSocket("auth:signin:completed", ctx),
|
|
3813
3827
|
"auth:signout:requested": async (_, ctx) => {
|
|
3814
3828
|
ctx.notifier.info("WS: sign-out requested -> disconnecting");
|
|
3815
3829
|
this.stopped = true;
|
|
@@ -3828,7 +3842,10 @@ var WebSocketOrchestrator = class {
|
|
|
3828
3842
|
ctx.notifier.info("WS: reconnect requested by auth", { scope: "system" /* System */ });
|
|
3829
3843
|
this.retryAttempt = 0;
|
|
3830
3844
|
this.clearCountdown();
|
|
3831
|
-
this.socket
|
|
3845
|
+
if (this.socket) {
|
|
3846
|
+
this.socket.stop();
|
|
3847
|
+
this.socket = null;
|
|
3848
|
+
}
|
|
3832
3849
|
this.ensureStarted(ctx, "auth:ws:reconnect");
|
|
3833
3850
|
},
|
|
3834
3851
|
// the only sender
|
|
@@ -3847,6 +3864,11 @@ var WebSocketOrchestrator = class {
|
|
|
3847
3864
|
ctx.notifier.info(`WS: connect loop already running (reason=${reason})`, { scope: "system" /* System */ });
|
|
3848
3865
|
return;
|
|
3849
3866
|
}
|
|
3867
|
+
const ws = this.socket?.getSocket();
|
|
3868
|
+
if (ws && ws.readyState <= 1) {
|
|
3869
|
+
ctx.notifier.info(`WS: already connected/connecting, skipping (reason=${reason})`, { scope: "system" /* System */ });
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3850
3872
|
this.connectLoopInFlight = this.startOrJoinConnectLoop(ctx, reason).finally(() => {
|
|
3851
3873
|
this.connectLoopInFlight = null;
|
|
3852
3874
|
});
|
|
@@ -3861,32 +3883,47 @@ var WebSocketOrchestrator = class {
|
|
|
3861
3883
|
this.lastUrl = url;
|
|
3862
3884
|
ctx.notifier.info(`WS: ensure connection (reason=${reason}) url=${url}`, { scope: "system" /* System */ });
|
|
3863
3885
|
if (!this.socket) {
|
|
3864
|
-
|
|
3886
|
+
const sock = new RecoverableSocket(url, {
|
|
3865
3887
|
abortSignal: ctx.signal,
|
|
3866
3888
|
connectionTimeoutMs: 1e4,
|
|
3867
3889
|
heartbeatIntervalMs: 2e4,
|
|
3868
3890
|
pongTimeoutMs: 1e4
|
|
3869
3891
|
});
|
|
3870
|
-
this.socket
|
|
3871
|
-
|
|
3892
|
+
this.socket = sock;
|
|
3893
|
+
sock.registerOnStateChangeCallback((state) => this.handleStateChange(state, ctx));
|
|
3894
|
+
sock.registerOnErrorCallback((error) => {
|
|
3872
3895
|
ctx.notifier.error(`WS: transport error: ${error.message}`);
|
|
3873
3896
|
});
|
|
3874
|
-
|
|
3897
|
+
sock.registerOnConnectedCallback((_ws) => {
|
|
3898
|
+
if (this.socket !== sock)
|
|
3899
|
+
return;
|
|
3875
3900
|
this.retryAttempt = 0;
|
|
3876
3901
|
this.clearCountdown();
|
|
3877
|
-
ctx.notifier.success(`WS: connected (${url})`);
|
|
3902
|
+
ctx.notifier.success(ctx.flags.debug ? `WS: connected (${url})` : `WS: connected`);
|
|
3878
3903
|
ctx.bus.emit("ws:connected", { url, attempt: 0 });
|
|
3879
3904
|
});
|
|
3880
|
-
|
|
3905
|
+
sock.registerOnDisconnectedCallback((code, reasonStr) => {
|
|
3906
|
+
if (this.socket !== sock) {
|
|
3907
|
+
ctx.notifier.info(`WS: ignoring stale disconnect (code=${code})`, { scope: "system" /* System */ });
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3881
3910
|
ctx.notifier.warn(`WS: disconnected code=${code} reason=${reasonStr || "-"}`);
|
|
3882
3911
|
ctx.bus.emit("ws:disconnected", { code, reason: reasonStr });
|
|
3883
3912
|
if (code === 4001) {
|
|
3884
3913
|
ctx.notifier.info("WS: auth disconnect (4001) -> waiting for auth", { scope: "system" /* System */ });
|
|
3885
3914
|
return;
|
|
3886
3915
|
}
|
|
3916
|
+
if (code === 4011) {
|
|
3917
|
+
this.stopped = true;
|
|
3918
|
+
this.socket?.stop();
|
|
3919
|
+
this.socket = null;
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3887
3922
|
this.ensureStarted(ctx, "socket:disconnected");
|
|
3888
3923
|
});
|
|
3889
|
-
|
|
3924
|
+
sock.registerOnMessageCallback((data) => {
|
|
3925
|
+
if (this.socket !== sock)
|
|
3926
|
+
return;
|
|
3890
3927
|
const text = typeof data === "string" ? data : data.toString("utf8");
|
|
3891
3928
|
ctx.bus.emit("ws:rx", { text });
|
|
3892
3929
|
});
|
|
@@ -4557,6 +4594,7 @@ var AuthOrchestrator = class {
|
|
|
4557
4594
|
connectionId = null;
|
|
4558
4595
|
sessionId = null;
|
|
4559
4596
|
accessToken;
|
|
4597
|
+
rejectedByConflict = false;
|
|
4560
4598
|
refreshGroupKey = "auth:refresh";
|
|
4561
4599
|
refreshCycle = 0;
|
|
4562
4600
|
refreshScheduler = null;
|
|
@@ -4590,6 +4628,11 @@ var AuthOrchestrator = class {
|
|
|
4590
4628
|
"Auth: signin completed; requesting ws reconnect",
|
|
4591
4629
|
{ scope: "system" /* System */ }
|
|
4592
4630
|
);
|
|
4631
|
+
this.rejectedByConflict = false;
|
|
4632
|
+
if (this.refreshRetryScheduler) {
|
|
4633
|
+
this.refreshRetryScheduler.stop();
|
|
4634
|
+
this.refreshRetryScheduler = null;
|
|
4635
|
+
}
|
|
4593
4636
|
this.ensureAccessExpPersisted(ctx, "signin");
|
|
4594
4637
|
await this.ensureRefreshScheduling(ctx, "signin");
|
|
4595
4638
|
ctx.bus.emit("auth:ws:reconnect", void 0);
|
|
@@ -4646,6 +4689,14 @@ var AuthOrchestrator = class {
|
|
|
4646
4689
|
{ scope: "system" /* System */ }
|
|
4647
4690
|
);
|
|
4648
4691
|
this.resetConnectionState();
|
|
4692
|
+
if (p.code === 4011) {
|
|
4693
|
+
ctx.notifier.warn(
|
|
4694
|
+
'Connection rejected - another device is active on this workspace. Run "signin" to try again.'
|
|
4695
|
+
);
|
|
4696
|
+
this.rejectedByConflict = true;
|
|
4697
|
+
ctx.bus.emit("ws:disconnect", { reason: "REPLACEMENT_REJECTED" });
|
|
4698
|
+
return;
|
|
4699
|
+
}
|
|
4649
4700
|
if (p.code === 4001) {
|
|
4650
4701
|
const refreshRes = await ctx.engine.secrets.get("refreshToken");
|
|
4651
4702
|
if (!refreshRes.ok || !refreshRes.value) {
|
|
@@ -4668,7 +4719,8 @@ var AuthOrchestrator = class {
|
|
|
4668
4719
|
// --- boot path unchanged except it must schedule refresh always ---
|
|
4669
4720
|
async onEventConfigLoaded(config, ctx) {
|
|
4670
4721
|
ctx.notifier.info(
|
|
4671
|
-
"Initializing with cached configuration"
|
|
4722
|
+
"Initializing with cached configuration",
|
|
4723
|
+
{ scope: "system" /* System */ }
|
|
4672
4724
|
);
|
|
4673
4725
|
if (!config.device?.id) {
|
|
4674
4726
|
ctx.notifier.error("Device ID is not available");
|
|
@@ -4697,7 +4749,6 @@ var AuthOrchestrator = class {
|
|
|
4697
4749
|
ctx.notifier.warn("Auth: not signed in; run signin");
|
|
4698
4750
|
return;
|
|
4699
4751
|
}
|
|
4700
|
-
ctx.bus.emit("auth:ws:reconnect", void 0);
|
|
4701
4752
|
}
|
|
4702
4753
|
isAccessLikelyUsable(ctx) {
|
|
4703
4754
|
const jwt = this.accessToken;
|
|
@@ -4714,7 +4765,8 @@ var AuthOrchestrator = class {
|
|
|
4714
4765
|
async waitForApproval(api, startResponse, ctx) {
|
|
4715
4766
|
const deadline = Date.now() + startResponse.expires_in * 1e3;
|
|
4716
4767
|
ctx.notifier.info(
|
|
4717
|
-
"Waiting for approval... (streaming)"
|
|
4768
|
+
"Waiting for approval... (streaming)",
|
|
4769
|
+
{ scope: "system" /* System */ }
|
|
4718
4770
|
);
|
|
4719
4771
|
try {
|
|
4720
4772
|
return await api.deviceAuthStream({
|
|
@@ -4811,7 +4863,8 @@ var AuthOrchestrator = class {
|
|
|
4811
4863
|
}
|
|
4812
4864
|
if (streamResp.needs_proof) {
|
|
4813
4865
|
ctx.notifier.info(
|
|
4814
|
-
"Sending proof-of-possession signature..."
|
|
4866
|
+
"Sending proof-of-possession signature...",
|
|
4867
|
+
{ scope: "system" /* System */ }
|
|
4815
4868
|
);
|
|
4816
4869
|
const privKeyObj = crypto8.createPrivateKey(privateKeyPem);
|
|
4817
4870
|
const challengeBytes = Buffer.from(
|
|
@@ -4826,9 +4879,9 @@ var AuthOrchestrator = class {
|
|
|
4826
4879
|
challenge: startResponse.challenge,
|
|
4827
4880
|
signature_b64: signatureB64
|
|
4828
4881
|
});
|
|
4829
|
-
ctx.notifier.info("Proof-of-possession verified");
|
|
4882
|
+
ctx.notifier.info("Proof-of-possession verified", { scope: "system" /* System */ });
|
|
4830
4883
|
}
|
|
4831
|
-
ctx.notifier.info("Fetching tokens...");
|
|
4884
|
+
ctx.notifier.info("Fetching tokens...", { scope: "system" /* System */ });
|
|
4832
4885
|
const tokenResp = await api.deviceAuthToken({
|
|
4833
4886
|
device_code: startResponse.device_code
|
|
4834
4887
|
});
|
|
@@ -4962,6 +5015,9 @@ var AuthOrchestrator = class {
|
|
|
4962
5015
|
ctx.bus.emit("auth:session_conflict", msg);
|
|
4963
5016
|
void this.handleSessionConflict(ctx, msg);
|
|
4964
5017
|
return;
|
|
5018
|
+
case "quiesce":
|
|
5019
|
+
void this.handleQuiesce(ctx, msg);
|
|
5020
|
+
return;
|
|
4965
5021
|
default:
|
|
4966
5022
|
return;
|
|
4967
5023
|
}
|
|
@@ -4972,14 +5028,41 @@ var AuthOrchestrator = class {
|
|
|
4972
5028
|
this.conflictTimer = null;
|
|
4973
5029
|
}
|
|
4974
5030
|
this.pendingConflictRequest = msg;
|
|
5031
|
+
ctx.notifier.warn(
|
|
5032
|
+
`Another device is already connected to this workspace. Type "replace" to disconnect it, or this connection will be rejected in ${Math.round(msg.deadline_ms / 1e3)}s.`
|
|
5033
|
+
);
|
|
4975
5034
|
const deadlineMs = Math.min(msg.deadline_ms, 15e3);
|
|
4976
5035
|
this.conflictTimer = setTimeout(() => {
|
|
4977
5036
|
this.conflictTimer = null;
|
|
4978
5037
|
if (this.pendingConflictRequest?.new_session_id === msg.new_session_id) {
|
|
5038
|
+
ctx.notifier.info("No response - keeping existing device connected.");
|
|
4979
5039
|
this.sendSessionConflictResolve(ctx, "KEEP_OLD");
|
|
4980
5040
|
}
|
|
4981
5041
|
}, deadlineMs);
|
|
4982
5042
|
}
|
|
5043
|
+
async handleQuiesce(ctx, msg) {
|
|
5044
|
+
ctx.notifier.warn(
|
|
5045
|
+
`This device is being replaced by another connection. Finishing in-flight operations...`
|
|
5046
|
+
);
|
|
5047
|
+
const hasActiveJobs = ctx.engine.hasRunningJobs?.() ?? false;
|
|
5048
|
+
if (hasActiveJobs) {
|
|
5049
|
+
const deadline = Math.min(msg.deadline_ms - 2e3, 12e3);
|
|
5050
|
+
const start = Date.now();
|
|
5051
|
+
while (Date.now() - start < deadline) {
|
|
5052
|
+
if (!(ctx.engine.hasRunningJobs?.() ?? false))
|
|
5053
|
+
break;
|
|
5054
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
const status = ctx.engine.hasRunningJobs?.() ?? false ? "BUSY" : "IDLE";
|
|
5058
|
+
this.send(ctx, {
|
|
5059
|
+
type: "quiesce_ack",
|
|
5060
|
+
session_id: msg.session_id,
|
|
5061
|
+
status
|
|
5062
|
+
});
|
|
5063
|
+
ctx.notifier.info("Device replaced by new connection. Disconnecting.");
|
|
5064
|
+
this.socket?.stop();
|
|
5065
|
+
}
|
|
4983
5066
|
sendSessionConflictResolve(ctx, choice) {
|
|
4984
5067
|
const req = this.pendingConflictRequest;
|
|
4985
5068
|
if (!req)
|
|
@@ -5241,6 +5324,10 @@ var AuthOrchestrator = class {
|
|
|
5241
5324
|
if (this.accessToken && !ctx.engine.configurationContext.configuration.tokens?.accessTokenExpSec) {
|
|
5242
5325
|
this.ensureAccessExpPersisted(ctx, reason);
|
|
5243
5326
|
}
|
|
5327
|
+
if (this.refreshRetryScheduler) {
|
|
5328
|
+
this.refreshRetryScheduler.stop();
|
|
5329
|
+
this.refreshRetryScheduler = null;
|
|
5330
|
+
}
|
|
5244
5331
|
this.scheduleSilentRefresh(ctx, reason);
|
|
5245
5332
|
}
|
|
5246
5333
|
async scheduleSilentRefresh(ctx, reason) {
|
|
@@ -7676,6 +7763,7 @@ var ToolsOrchestrator = class {
|
|
|
7676
7763
|
const availableDirectoryIds = config.directories.map((r) => r.id).join(", ") || "none";
|
|
7677
7764
|
const availableDirectoryPaths = config.directories.map((r) => r.path).join(", ") || "none";
|
|
7678
7765
|
ctx.notifier.info(`Tool call: ${toolName} (job=${jobId}, directory_id=${directoryId || "MISSING"}, available_ids=[${availableDirectoryIds}], available_paths=[${availableDirectoryPaths}], directory_count=${config.directories.length})`, { scope: "system" /* System */ });
|
|
7766
|
+
ctx.notifier.info(`Executing: ${toolName}`);
|
|
7679
7767
|
const toolStartTime = Date.now();
|
|
7680
7768
|
try {
|
|
7681
7769
|
const toolRegistry = new ToolExecuterRegistry(config);
|
|
@@ -7686,9 +7774,9 @@ var ToolsOrchestrator = class {
|
|
|
7686
7774
|
const timing = calculateTiming(toolStartTime);
|
|
7687
7775
|
const result = createToolResult(toolName, jobId, raw, timing.execution_sec);
|
|
7688
7776
|
if (result.ok) {
|
|
7689
|
-
ctx.notifier.success(result.toString()
|
|
7777
|
+
ctx.notifier.success(result.toString());
|
|
7690
7778
|
} else {
|
|
7691
|
-
ctx.notifier.error(result.toString()
|
|
7779
|
+
ctx.notifier.error(result.toString());
|
|
7692
7780
|
}
|
|
7693
7781
|
} catch (e) {
|
|
7694
7782
|
const toolResult = formatToolErrorResponse(jobId, toolName, e, toolStartTime);
|
|
@@ -7700,7 +7788,7 @@ var ToolsOrchestrator = class {
|
|
|
7700
7788
|
error: e instanceof Error ? { code: "INTERNAL", message: e.message } : { code: "INTERNAL", message: String(e) }
|
|
7701
7789
|
};
|
|
7702
7790
|
const result = createToolResult(toolName, jobId, errorResult, timing.execution_sec);
|
|
7703
|
-
ctx.notifier.error(result.toString()
|
|
7791
|
+
ctx.notifier.error(result.toString());
|
|
7704
7792
|
}
|
|
7705
7793
|
}
|
|
7706
7794
|
};
|