@linkshell/wire 0.2.30 → 0.2.32
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/dist/direct.d.ts +2 -0
- package/dist/direct.js +3 -1
- package/dist/direct.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/live-activity.d.ts +59 -2
- package/dist/live-activity.js +26 -0
- package/dist/live-activity.js.map +1 -1
- package/dist/lsa.d.ts +308 -0
- package/dist/lsa.js +144 -0
- package/dist/lsa.js.map +1 -0
- package/dist/model.d.ts +66 -0
- package/dist/model.js +19 -0
- package/dist/model.js.map +1 -1
- package/dist/peer.d.ts +3 -1
- package/dist/peer.js +60 -22
- package/dist/peer.js.map +1 -1
- package/dist/relay-client.d.ts +7 -1
- package/dist/relay-client.js +83 -6
- package/dist/relay-client.js.map +1 -1
- package/dist/relay.d.ts +1 -0
- package/dist/relay.js +1 -1
- package/dist/relay.js.map +1 -1
- package/dist/rpc.d.ts +593 -8
- package/dist/rpc.js +132 -8
- package/dist/rpc.js.map +1 -1
- package/dist/updates.d.ts +7 -3
- package/dist/updates.js +4 -0
- package/dist/updates.js.map +1 -1
- package/dist/usage.d.ts +32 -0
- package/dist/usage.js +93 -5
- package/dist/usage.js.map +1 -1
- package/dist/watch.d.ts +7 -7
- package/package.json +1 -1
- package/src/direct.ts +4 -1
- package/src/index.ts +1 -0
- package/src/live-activity.ts +35 -0
- package/src/lsa.ts +159 -0
- package/src/model.ts +25 -0
- package/src/peer.ts +56 -18
- package/src/relay-client.ts +84 -8
- package/src/relay.ts +1 -1
- package/src/rpc.ts +143 -7
- package/src/updates.ts +4 -0
- package/src/usage.ts +117 -5
package/dist/usage.js
CHANGED
|
@@ -4,18 +4,106 @@ export const usageQuerySchema = z.object({
|
|
|
4
4
|
agent: z.string().min(1).max(128).default("all"),
|
|
5
5
|
});
|
|
6
6
|
export const usageTotalsSchema = z.object({
|
|
7
|
-
records: z.number(),
|
|
8
|
-
|
|
7
|
+
records: z.number(),
|
|
8
|
+
sessions: z.number(),
|
|
9
|
+
inputTokens: z.number(),
|
|
10
|
+
outputTokens: z.number(),
|
|
11
|
+
cacheReadTokens: z.number(),
|
|
12
|
+
cacheWriteTokens: z.number(),
|
|
13
|
+
totalTokens: z.number(),
|
|
14
|
+
estimatedCostUsd: z.number().nonnegative().optional(),
|
|
15
|
+
});
|
|
16
|
+
export const usageRemoteStatsSchema = z.object({
|
|
17
|
+
remoteTurns: z.number().int().nonnegative(),
|
|
18
|
+
remoteApprovals: z.number().int().nonnegative(),
|
|
9
19
|
});
|
|
10
20
|
export const usageReportSchema = z.object({
|
|
11
|
-
generatedAt: z.number(),
|
|
21
|
+
generatedAt: z.number(),
|
|
22
|
+
days: z.number(),
|
|
23
|
+
agent: z.string(),
|
|
24
|
+
timezone: z.string(),
|
|
12
25
|
totals: usageTotalsSchema,
|
|
13
26
|
daily: z.array(usageTotalsSchema.extend({ date: z.string() })),
|
|
14
27
|
models: z.array(usageTotalsSchema.extend({ agent: z.string(), model: z.string() })),
|
|
15
28
|
agents: z.array(z.object({
|
|
16
|
-
id: z.string(),
|
|
17
|
-
|
|
29
|
+
id: z.string(),
|
|
30
|
+
label: z.string(),
|
|
31
|
+
source: z.enum(["native", "acp"]),
|
|
32
|
+
status: z.enum(["available", "no_data"]),
|
|
33
|
+
records: z.number(),
|
|
34
|
+
totalTokens: z.number(),
|
|
35
|
+
note: z.string(),
|
|
36
|
+
estimatedCostUsd: z.number().nonnegative().optional(),
|
|
18
37
|
})).optional(),
|
|
38
|
+
estimatedCostUsd: z.number().nonnegative().optional(),
|
|
39
|
+
remoteStats: usageRemoteStatsSchema.optional(),
|
|
19
40
|
warnings: z.array(z.string()),
|
|
20
41
|
});
|
|
42
|
+
/**
|
|
43
|
+
* Reference public API pricing (USD per 1M tokens), ordered most-specific first.
|
|
44
|
+
* Subscription users (ChatGPT Plus/Pro, Claude Pro/Max) pay flat fees; this
|
|
45
|
+
* estimates the equivalent API value of work orchestrated through LinkShell.
|
|
46
|
+
*/
|
|
47
|
+
const MODEL_PRICING_RULES = [
|
|
48
|
+
// Anthropic Claude
|
|
49
|
+
{ match: /claude[-_.]?opus[-_.]?(?:4[._][56]|5)/i, pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 } },
|
|
50
|
+
{ match: /opus/i, pricing: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 } },
|
|
51
|
+
{ match: /sonnet/i, pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 } },
|
|
52
|
+
{ match: /claude[-_.]?haiku[-_.]?4[._]5/i, pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 } },
|
|
53
|
+
{ match: /haiku/i, pricing: { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 } },
|
|
54
|
+
// OpenAI / Codex
|
|
55
|
+
{ match: /(?:gpt-5|o1|o3)[-_]pro/i, pricing: { input: 15, output: 60, cacheRead: 3.75, cacheWrite: 15 } },
|
|
56
|
+
{ match: /gpt-5[-_.]?mini/i, pricing: { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0.25 } },
|
|
57
|
+
{ match: /gpt-5[-_.]?nano/i, pricing: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0.05 } },
|
|
58
|
+
{ match: /codex[-_.]?mini/i, pricing: { input: 1.5, output: 6, cacheRead: 0.375, cacheWrite: 1.5 } },
|
|
59
|
+
{ match: /gpt-5|codex/i, pricing: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 1.25 } },
|
|
60
|
+
{ match: /gpt-4\.1[-_.]?mini/i, pricing: { input: 0.4, output: 1.6, cacheRead: 0.1, cacheWrite: 0.4 } },
|
|
61
|
+
{ match: /gpt-4\.1[-_.]?nano/i, pricing: { input: 0.1, output: 0.4, cacheRead: 0.025, cacheWrite: 0.1 } },
|
|
62
|
+
{ match: /gpt-4\.1/i, pricing: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 2 } },
|
|
63
|
+
{ match: /gpt-4o[-_.]?mini/i, pricing: { input: 0.15, output: 0.6, cacheRead: 0.075, cacheWrite: 0.15 } },
|
|
64
|
+
{ match: /gpt-4o/i, pricing: { input: 2.5, output: 10, cacheRead: 1.25, cacheWrite: 2.5 } },
|
|
65
|
+
{ match: /o[34][-_.]?mini/i, pricing: { input: 1.1, output: 4.4, cacheRead: 0.275, cacheWrite: 1.1 } },
|
|
66
|
+
{ match: /\bo3\b|\bo1\b/i, pricing: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 2 } },
|
|
67
|
+
// Google Gemini
|
|
68
|
+
{ match: /gemini.*flash/i, pricing: { input: 0.3, output: 2.5, cacheRead: 0.075, cacheWrite: 0.3 } },
|
|
69
|
+
{ match: /gemini/i, pricing: { input: 1.25, output: 10, cacheRead: 0.3125, cacheWrite: 1.25 } },
|
|
70
|
+
// xAI Grok & DeepSeek
|
|
71
|
+
{ match: /grok/i, pricing: { input: 3, output: 15, cacheRead: 0.75, cacheWrite: 3 } },
|
|
72
|
+
{ match: /deepseek/i, pricing: { input: 0.27, output: 1.1, cacheRead: 0.07, cacheWrite: 0.27 } },
|
|
73
|
+
];
|
|
74
|
+
const DEFAULT_PRICING_PER_MILLION = {
|
|
75
|
+
input: 2,
|
|
76
|
+
output: 8,
|
|
77
|
+
cacheRead: 0.2,
|
|
78
|
+
cacheWrite: 2,
|
|
79
|
+
};
|
|
80
|
+
export function modelPricingPerMillionUsd(model) {
|
|
81
|
+
const normalized = model.trim();
|
|
82
|
+
for (const rule of MODEL_PRICING_RULES) {
|
|
83
|
+
if (rule.match.test(normalized))
|
|
84
|
+
return rule.pricing;
|
|
85
|
+
}
|
|
86
|
+
return DEFAULT_PRICING_PER_MILLION;
|
|
87
|
+
}
|
|
88
|
+
function safeTokenCount(value) {
|
|
89
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Pure reference cost estimator (in USD) for a model and non-overlapping token bucket totals.
|
|
93
|
+
*/
|
|
94
|
+
export function estimateModelCostUsd(model, totals) {
|
|
95
|
+
const input = safeTokenCount(totals.inputTokens);
|
|
96
|
+
const output = safeTokenCount(totals.outputTokens);
|
|
97
|
+
const cacheRead = safeTokenCount(totals.cacheReadTokens);
|
|
98
|
+
const cacheWrite = safeTokenCount(totals.cacheWriteTokens);
|
|
99
|
+
if (input === 0 && output === 0 && cacheRead === 0 && cacheWrite === 0)
|
|
100
|
+
return 0;
|
|
101
|
+
const rate = modelPricingPerMillionUsd(model);
|
|
102
|
+
const rawUsd = (input * rate.input +
|
|
103
|
+
output * rate.output +
|
|
104
|
+
cacheRead * rate.cacheRead +
|
|
105
|
+
cacheWrite * rate.cacheWrite) /
|
|
106
|
+
1_000_000;
|
|
107
|
+
return Math.round(rawUsd * 1_000_000) / 1_000_000;
|
|
108
|
+
}
|
|
21
109
|
//# sourceMappingURL=usage.js.map
|
package/dist/usage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;CACjD,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
|
package/dist/watch.d.ts
CHANGED
|
@@ -31,8 +31,8 @@ export declare const watchConfigOptionSchema: z.ZodObject<{
|
|
|
31
31
|
category: z.ZodDefault<z.ZodEnum<{
|
|
32
32
|
model: "model";
|
|
33
33
|
other: "other";
|
|
34
|
-
model_config: "model_config";
|
|
35
34
|
mode: "mode";
|
|
35
|
+
model_config: "model_config";
|
|
36
36
|
effort: "effort";
|
|
37
37
|
}>>;
|
|
38
38
|
current: z.ZodString;
|
|
@@ -49,10 +49,10 @@ export declare const watchActionSchema: z.ZodObject<{
|
|
|
49
49
|
label: z.ZodString;
|
|
50
50
|
role: z.ZodEnum<{
|
|
51
51
|
choice: "choice";
|
|
52
|
+
always: "always";
|
|
52
53
|
stop: "stop";
|
|
53
54
|
reject: "reject";
|
|
54
55
|
allow: "allow";
|
|
55
|
-
always: "always";
|
|
56
56
|
}>;
|
|
57
57
|
kind: z.ZodEnum<{
|
|
58
58
|
permission: "permission";
|
|
@@ -126,8 +126,8 @@ export declare const watchSessionSchema: z.ZodObject<{
|
|
|
126
126
|
category: z.ZodDefault<z.ZodEnum<{
|
|
127
127
|
model: "model";
|
|
128
128
|
other: "other";
|
|
129
|
-
model_config: "model_config";
|
|
130
129
|
mode: "mode";
|
|
130
|
+
model_config: "model_config";
|
|
131
131
|
effort: "effort";
|
|
132
132
|
}>>;
|
|
133
133
|
current: z.ZodString;
|
|
@@ -163,10 +163,10 @@ export declare const watchSessionSchema: z.ZodObject<{
|
|
|
163
163
|
label: z.ZodString;
|
|
164
164
|
role: z.ZodEnum<{
|
|
165
165
|
choice: "choice";
|
|
166
|
+
always: "always";
|
|
166
167
|
stop: "stop";
|
|
167
168
|
reject: "reject";
|
|
168
169
|
allow: "allow";
|
|
169
|
-
always: "always";
|
|
170
170
|
}>;
|
|
171
171
|
kind: z.ZodEnum<{
|
|
172
172
|
permission: "permission";
|
|
@@ -292,8 +292,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
292
292
|
category: z.ZodDefault<z.ZodEnum<{
|
|
293
293
|
model: "model";
|
|
294
294
|
other: "other";
|
|
295
|
-
model_config: "model_config";
|
|
296
295
|
mode: "mode";
|
|
296
|
+
model_config: "model_config";
|
|
297
297
|
effort: "effort";
|
|
298
298
|
}>>;
|
|
299
299
|
current: z.ZodString;
|
|
@@ -329,10 +329,10 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
329
329
|
label: z.ZodString;
|
|
330
330
|
role: z.ZodEnum<{
|
|
331
331
|
choice: "choice";
|
|
332
|
+
always: "always";
|
|
332
333
|
stop: "stop";
|
|
333
334
|
reject: "reject";
|
|
334
335
|
allow: "allow";
|
|
335
|
-
always: "always";
|
|
336
336
|
}>;
|
|
337
337
|
kind: z.ZodEnum<{
|
|
338
338
|
permission: "permission";
|
|
@@ -428,10 +428,10 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
428
428
|
label: z.ZodString;
|
|
429
429
|
role: z.ZodEnum<{
|
|
430
430
|
choice: "choice";
|
|
431
|
+
always: "always";
|
|
431
432
|
stop: "stop";
|
|
432
433
|
reject: "reject";
|
|
433
434
|
allow: "allow";
|
|
434
|
-
always: "always";
|
|
435
435
|
}>;
|
|
436
436
|
kind: z.ZodEnum<{
|
|
437
437
|
permission: "permission";
|
package/package.json
CHANGED
package/src/direct.ts
CHANGED
|
@@ -11,6 +11,9 @@ export const DIRECT_LABEL = "linkshell-bulk";
|
|
|
11
11
|
/** The most stream bytes in one message: every WebRTC stack takes this size. */
|
|
12
12
|
export const DIRECT_CHUNK = 16 * 1024;
|
|
13
13
|
|
|
14
|
+
/** Hard cap on decoded direct frames so a malformed frame cannot exhaust receiver memory. */
|
|
15
|
+
export const DIRECT_MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* STUN servers used when a host names none: they only tell each side its
|
|
16
19
|
* public address. (Several, because none of them is reachable everywhere.)
|
|
@@ -41,7 +44,7 @@ export function encodeDirectFrame(frame: DirectFrame): Uint8Array {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
export function decodeDirectFrame(bytes: Uint8Array): DirectFrame | undefined {
|
|
44
|
-
if (bytes.length < 5) return undefined;
|
|
47
|
+
if (bytes.length < 5 || bytes.length > DIRECT_MAX_FRAME_BYTES) return undefined;
|
|
45
48
|
const stream = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(1);
|
|
46
49
|
const payload = bytes.subarray(5);
|
|
47
50
|
if (bytes[0] === DATA) return { type: "data", stream, data: payload };
|
package/src/index.ts
CHANGED
package/src/live-activity.ts
CHANGED
|
@@ -49,6 +49,41 @@ export const liveActivitySubscriptionSchema = z.object({
|
|
|
49
49
|
});
|
|
50
50
|
export type LiveActivitySubscription = z.infer<typeof liveActivitySubscriptionSchema>;
|
|
51
51
|
|
|
52
|
+
export const liveActivityPlatformSchema = z.enum(["ios", "android"]);
|
|
53
|
+
export type LiveActivityPlatform = z.infer<typeof liveActivityPlatformSchema>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Registers an iOS 17.2+ ActivityKit `pushToStartToken` (or Android background wakeup endpoint)
|
|
57
|
+
* so the host can start a Live Activity or wakeup alert even when the app was never opened first.
|
|
58
|
+
*/
|
|
59
|
+
export const liveActivityPushToStartSchema = z.object({
|
|
60
|
+
deviceId: z.string().min(1).max(256),
|
|
61
|
+
pushToStartToken: z.string().min(1).max(1024),
|
|
62
|
+
platform: liveActivityPlatformSchema,
|
|
63
|
+
serviceUrl: z.url().max(256),
|
|
64
|
+
grant: z.string().regex(/^[a-f0-9]{64}$/),
|
|
65
|
+
expiresAt: z.number().int().positive(),
|
|
66
|
+
computerKey: z.string().min(1).max(512),
|
|
67
|
+
});
|
|
68
|
+
export type LiveActivityPushToStart = z.infer<typeof liveActivityPushToStartSchema>;
|
|
69
|
+
|
|
70
|
+
export const wakeupEventTypeSchema = z.enum(["approval", "question", "error", "completed"]);
|
|
71
|
+
export type WakeupEventType = z.infer<typeof wakeupEventTypeSchema>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Zero-plaintext wakeup notice payload: carries only routing identifiers and transition kind,
|
|
75
|
+
* never source code, file paths, or prompt text, preserving end-to-end privacy.
|
|
76
|
+
*/
|
|
77
|
+
export const wakeupNoticeSchema = z.object({
|
|
78
|
+
machineId: z.string().min(1).max(256),
|
|
79
|
+
sessionId: z.string().min(1).max(256),
|
|
80
|
+
eventType: wakeupEventTypeSchema,
|
|
81
|
+
createdAt: z.number().int().positive(),
|
|
82
|
+
});
|
|
83
|
+
export type WakeupNotice = z.infer<typeof wakeupNoticeSchema>;
|
|
84
|
+
export const liveActivityWakeupNoticeSchema = wakeupNoticeSchema;
|
|
85
|
+
export type LiveActivityWakeupNotice = WakeupNotice;
|
|
86
|
+
|
|
52
87
|
// ActivityKit accepts at most 4 KB. Preserve request identifiers exactly; drop
|
|
53
88
|
// whole buttons when necessary, so a truncated choice can never be submitted.
|
|
54
89
|
export function liveActivityContent(payload: LiveActivityPayload): LiveActivityContentState {
|
package/src/lsa.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LinkShell Agent (LSA) Phase 1 RPC contracts: deterministic task ledger,
|
|
5
|
+
* automated post-turn acceptance verification, memories, and due followups.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const lsaTaskStatusSchema = z.enum([
|
|
9
|
+
"queued",
|
|
10
|
+
"running",
|
|
11
|
+
"verifying",
|
|
12
|
+
"waiting_decision",
|
|
13
|
+
"completed",
|
|
14
|
+
"failed",
|
|
15
|
+
"cancelled",
|
|
16
|
+
]);
|
|
17
|
+
export type LsaTaskStatus = z.infer<typeof lsaTaskStatusSchema>;
|
|
18
|
+
|
|
19
|
+
export const lsaDecisionSchema = z.enum(["accept", "retry", "cancel"]);
|
|
20
|
+
export type LsaDecision = z.infer<typeof lsaDecisionSchema>;
|
|
21
|
+
|
|
22
|
+
export const lsaTaskSchema = z.object({
|
|
23
|
+
id: z.string().min(1),
|
|
24
|
+
title: z.string().optional(),
|
|
25
|
+
goal: z.string().min(1),
|
|
26
|
+
status: lsaTaskStatusSchema,
|
|
27
|
+
cwd: z.string(),
|
|
28
|
+
worktree: z.boolean().optional(),
|
|
29
|
+
agent: z.string().optional(),
|
|
30
|
+
sessionId: z.string().optional(),
|
|
31
|
+
controllerEpoch: z.number().int().nonnegative().optional(),
|
|
32
|
+
acceptanceChecks: z.array(z.string()),
|
|
33
|
+
summary: z.string().optional(),
|
|
34
|
+
createdAt: z.number(),
|
|
35
|
+
updatedAt: z.number(),
|
|
36
|
+
});
|
|
37
|
+
export type LsaTask = z.infer<typeof lsaTaskSchema>;
|
|
38
|
+
|
|
39
|
+
export const lsaMemorySchema = z.object({
|
|
40
|
+
id: z.string().optional(),
|
|
41
|
+
scope: z.string().optional(),
|
|
42
|
+
key: z.string().min(1),
|
|
43
|
+
content: z.string(),
|
|
44
|
+
tags: z.array(z.string()),
|
|
45
|
+
updatedAt: z.number(),
|
|
46
|
+
});
|
|
47
|
+
export type LsaMemory = z.infer<typeof lsaMemorySchema>;
|
|
48
|
+
|
|
49
|
+
export const lsaFollowupSchema = z.object({
|
|
50
|
+
id: z.string().min(1),
|
|
51
|
+
taskId: z.string().optional(),
|
|
52
|
+
dueAt: z.number(),
|
|
53
|
+
note: z.string(),
|
|
54
|
+
done: z.boolean(),
|
|
55
|
+
triggeredAt: z.number().optional(),
|
|
56
|
+
});
|
|
57
|
+
export type LsaFollowup = z.infer<typeof lsaFollowupSchema>;
|
|
58
|
+
|
|
59
|
+
export const lsaCandidateProjectSchema = z.object({
|
|
60
|
+
cwd: z.string(),
|
|
61
|
+
name: z.string(),
|
|
62
|
+
score: z.number(),
|
|
63
|
+
remote: z.string().optional(),
|
|
64
|
+
reason: z.string().optional(),
|
|
65
|
+
});
|
|
66
|
+
export type LsaCandidateProject = z.infer<typeof lsaCandidateProjectSchema>;
|
|
67
|
+
|
|
68
|
+
export const lsaStatusSchema = z.object({
|
|
69
|
+
enabled: z.boolean(),
|
|
70
|
+
controllerEpoch: z.number().int().nonnegative(),
|
|
71
|
+
generation: z.number().int().positive(),
|
|
72
|
+
activeTasks: z.number().int().nonnegative(),
|
|
73
|
+
waitingDecisionTasks: z.number().int().nonnegative(),
|
|
74
|
+
dueFollowups: z.number().int().nonnegative(),
|
|
75
|
+
requiresYearlyTier: z.boolean(),
|
|
76
|
+
handoffSummary: z.string().optional(),
|
|
77
|
+
candidates: z.array(lsaCandidateProjectSchema).optional(),
|
|
78
|
+
});
|
|
79
|
+
export type LsaStatus = z.infer<typeof lsaStatusSchema>;
|
|
80
|
+
|
|
81
|
+
export const lsaMethods = {
|
|
82
|
+
"lsa.status": {
|
|
83
|
+
params: z.object({
|
|
84
|
+
query: z.string().max(200).optional(),
|
|
85
|
+
}),
|
|
86
|
+
result: lsaStatusSchema,
|
|
87
|
+
},
|
|
88
|
+
"lsa.tasks": {
|
|
89
|
+
params: z.object({
|
|
90
|
+
taskId: z.string().optional(),
|
|
91
|
+
status: lsaTaskStatusSchema.optional(),
|
|
92
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
93
|
+
}),
|
|
94
|
+
result: z.object({
|
|
95
|
+
tasks: z.array(lsaTaskSchema),
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
"lsa.dispatch": {
|
|
99
|
+
params: z.object({
|
|
100
|
+
goal: z.string().min(1).max(20_000),
|
|
101
|
+
title: z.string().max(200).optional(),
|
|
102
|
+
cwd: z.string().optional(),
|
|
103
|
+
agent: z.string().optional(),
|
|
104
|
+
worktree: z.boolean().optional(),
|
|
105
|
+
acceptanceChecks: z.array(z.string().max(2000)).max(50).optional(),
|
|
106
|
+
}),
|
|
107
|
+
result: z.object({
|
|
108
|
+
task: lsaTaskSchema,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
"lsa.steer": {
|
|
112
|
+
params: z.object({
|
|
113
|
+
taskId: z.string().min(1),
|
|
114
|
+
instruction: z.string().min(1).max(20_000),
|
|
115
|
+
controllerEpoch: z.number().int().nonnegative().optional(),
|
|
116
|
+
}),
|
|
117
|
+
result: z.object({
|
|
118
|
+
task: lsaTaskSchema,
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
"lsa.decide": {
|
|
122
|
+
params: z.object({
|
|
123
|
+
taskId: z.string().min(1),
|
|
124
|
+
decision: lsaDecisionSchema,
|
|
125
|
+
feedback: z.string().max(20_000).optional(),
|
|
126
|
+
controllerEpoch: z.number().int().nonnegative().optional(),
|
|
127
|
+
}),
|
|
128
|
+
result: z.object({
|
|
129
|
+
task: lsaTaskSchema,
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
"lsa.memories": {
|
|
133
|
+
params: z.object({
|
|
134
|
+
op: z.enum(["list", "search", "save", "delete"]).optional(),
|
|
135
|
+
query: z.string().max(500).optional(),
|
|
136
|
+
scope: z.string().max(120).optional(),
|
|
137
|
+
key: z.string().max(200).optional(),
|
|
138
|
+
content: z.string().max(20_000).optional(),
|
|
139
|
+
tags: z.array(z.string().max(80)).max(32).optional(),
|
|
140
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
141
|
+
}),
|
|
142
|
+
result: z.object({
|
|
143
|
+
memories: z.array(lsaMemorySchema),
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
"lsa.followups": {
|
|
147
|
+
params: z.object({
|
|
148
|
+
op: z.enum(["list", "schedule", "complete", "delete"]).optional(),
|
|
149
|
+
id: z.string().optional(),
|
|
150
|
+
taskId: z.string().optional(),
|
|
151
|
+
dueAt: z.number().optional(),
|
|
152
|
+
note: z.string().max(2000).optional(),
|
|
153
|
+
includeDone: z.boolean().optional(),
|
|
154
|
+
}),
|
|
155
|
+
result: z.object({
|
|
156
|
+
followups: z.array(lsaFollowupSchema),
|
|
157
|
+
}),
|
|
158
|
+
},
|
|
159
|
+
} as const;
|
package/src/model.ts
CHANGED
|
@@ -302,6 +302,10 @@ export const gitInfoSchema = z.object({
|
|
|
302
302
|
head: z.string().optional(),
|
|
303
303
|
/** Uncommitted changes in the working directory (a new worktree starts from the last commit, without them). */
|
|
304
304
|
dirty: z.boolean(),
|
|
305
|
+
/** Normalized git origin remote (`host/owner/repo`), when configured. */
|
|
306
|
+
remote: z.string().optional(),
|
|
307
|
+
/** First commit hash of the repository, for cross-machine repo identity. */
|
|
308
|
+
rootCommit: z.string().optional(),
|
|
305
309
|
});
|
|
306
310
|
export type GitInfo = z.infer<typeof gitInfoSchema>;
|
|
307
311
|
|
|
@@ -356,6 +360,23 @@ export const machineInfoSchema = z.object({
|
|
|
356
360
|
});
|
|
357
361
|
export type MachineInfo = z.infer<typeof machineInfoSchema>;
|
|
358
362
|
|
|
363
|
+
export const keepAwakeModeSchema = z.enum(["smart", "always", "off"]);
|
|
364
|
+
export type KeepAwakeMode = z.infer<typeof keepAwakeModeSchema>;
|
|
365
|
+
|
|
366
|
+
export const machinePowerSchema = z.object({
|
|
367
|
+
onBattery: z.boolean(),
|
|
368
|
+
percent: z.number().min(0).max(100).nullable(),
|
|
369
|
+
charging: z.boolean().optional(),
|
|
370
|
+
});
|
|
371
|
+
export type MachinePower = z.infer<typeof machinePowerSchema>;
|
|
372
|
+
|
|
373
|
+
export const machineKeepAwakeSchema = z.object({
|
|
374
|
+
mode: keepAwakeModeSchema,
|
|
375
|
+
active: z.boolean(),
|
|
376
|
+
reason: z.string().optional(),
|
|
377
|
+
});
|
|
378
|
+
export type MachineKeepAwake = z.infer<typeof machineKeepAwakeSchema>;
|
|
379
|
+
|
|
359
380
|
/** Whole-computer resource use; CPU is a delta across all logical cores, not load average. */
|
|
360
381
|
export const machineMetricsSchema = z.object({
|
|
361
382
|
sampledAt: z.number().nonnegative(),
|
|
@@ -382,6 +403,10 @@ export const machineMetricsSchema = z.object({
|
|
|
382
403
|
/** Space available to this user; excludes filesystem reservations. */
|
|
383
404
|
availableBytes: z.number().nonnegative(),
|
|
384
405
|
}).optional(),
|
|
406
|
+
/** Battery and AC power state on laptops; absent on desktops or older hosts. */
|
|
407
|
+
power: machinePowerSchema.optional(),
|
|
408
|
+
/** Host idle-sleep assertion state; absent on older hosts. */
|
|
409
|
+
keepAwake: machineKeepAwakeSchema.optional(),
|
|
385
410
|
});
|
|
386
411
|
export type MachineMetrics = z.infer<typeof machineMetricsSchema>;
|
|
387
412
|
|
package/src/peer.ts
CHANGED
|
@@ -36,6 +36,7 @@ export type RequestHandler = (
|
|
|
36
36
|
method: string,
|
|
37
37
|
params: unknown,
|
|
38
38
|
id: RpcId,
|
|
39
|
+
signal: AbortSignal,
|
|
39
40
|
) => unknown | Promise<unknown>;
|
|
40
41
|
export type NotificationHandler = (method: string, params: unknown) => void;
|
|
41
42
|
|
|
@@ -62,6 +63,7 @@ interface Pending {
|
|
|
62
63
|
export class RpcPeer {
|
|
63
64
|
private nextId = 1;
|
|
64
65
|
private readonly pending = new Map<RpcId, Pending>();
|
|
66
|
+
private readonly inbound = new Map<RpcId, AbortController>();
|
|
65
67
|
private closed = false;
|
|
66
68
|
|
|
67
69
|
constructor(private readonly options: RpcPeerOptions) {}
|
|
@@ -75,6 +77,7 @@ export class RpcPeer {
|
|
|
75
77
|
if (limit > 0) {
|
|
76
78
|
entry.timer = setTimeout(() => {
|
|
77
79
|
this.pending.delete(id);
|
|
80
|
+
this.notify("$/cancelRequest", { id });
|
|
78
81
|
reject(new RpcError(RPC_APP_ERROR, `请求超时(${method},${limit} ms)`, { code: "timeout" }));
|
|
79
82
|
}, limit);
|
|
80
83
|
}
|
|
@@ -89,30 +92,42 @@ export class RpcPeer {
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
receive(text: string): void {
|
|
95
|
+
if (!text.includes("\n")) {
|
|
96
|
+
this.receiveLine(text);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
92
99
|
for (const line of text.split("\n")) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
const responses: Record<string, unknown>[] = [];
|
|
106
|
-
void Promise.all(message.map((entry) => Promise.resolve(this.dispatch(entry, (response) => { responses.push(response); }))))
|
|
107
|
-
.then(() => { if (responses.length) this.write(responses); });
|
|
108
|
-
} else void this.dispatch(message);
|
|
100
|
+
this.receiveLine(line);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private receiveLine(line: string): void {
|
|
105
|
+
if (!line || !line.trim()) return;
|
|
106
|
+
let message: Record<string, unknown> | Record<string, unknown>[];
|
|
107
|
+
try {
|
|
108
|
+
message = JSON.parse(line) as typeof message;
|
|
109
|
+
} catch {
|
|
110
|
+
return;
|
|
109
111
|
}
|
|
112
|
+
if (Array.isArray(message)) {
|
|
113
|
+
if (!message.length || message.length > 128) {
|
|
114
|
+
this.write({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "invalid JSON-RPC batch" } });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const responses: Record<string, unknown>[] = [];
|
|
118
|
+
void Promise.all(message.map((entry) => Promise.resolve(this.dispatch(entry, (response) => { responses.push(response); }))))
|
|
119
|
+
.then(() => { if (responses.length) this.write(responses); });
|
|
120
|
+
} else void this.dispatch(message);
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
/** Rejects every in-flight request; later calls fail immediately. */
|
|
113
124
|
close(reason = "连接已断开"): void {
|
|
114
125
|
if (this.closed) return;
|
|
115
126
|
this.closed = true;
|
|
127
|
+
for (const controller of this.inbound.values()) {
|
|
128
|
+
controller.abort();
|
|
129
|
+
}
|
|
130
|
+
this.inbound.clear();
|
|
116
131
|
for (const [id, entry] of this.pending) {
|
|
117
132
|
if (entry.timer) clearTimeout(entry.timer);
|
|
118
133
|
entry.reject(new RpcError(RPC_APP_ERROR, `${reason} (${entry.method})`, { code: "closed" }));
|
|
@@ -147,6 +162,17 @@ export class RpcPeer {
|
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
if (id === undefined) {
|
|
165
|
+
if (method === "$/cancelRequest") {
|
|
166
|
+
const cancelId = (message.params as { id?: unknown } | null | undefined)?.id;
|
|
167
|
+
if (typeof cancelId === "string" || typeof cancelId === "number") {
|
|
168
|
+
const controller = this.inbound.get(cancelId);
|
|
169
|
+
if (controller) {
|
|
170
|
+
this.inbound.delete(cancelId);
|
|
171
|
+
controller.abort();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
150
176
|
this.options.onNotification?.(method, message.params);
|
|
151
177
|
return;
|
|
152
178
|
}
|
|
@@ -156,14 +182,26 @@ export class RpcPeer {
|
|
|
156
182
|
respond({ jsonrpc: "2.0", id, error: { code: RPC_METHOD_NOT_FOUND, message: `no handler for ${method}` } });
|
|
157
183
|
return;
|
|
158
184
|
}
|
|
185
|
+
const controller = new AbortController();
|
|
186
|
+
if (this.closed) {
|
|
187
|
+
controller.abort();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.inbound.set(id, controller);
|
|
191
|
+
const cleanup = () => {
|
|
192
|
+
if (this.inbound.get(id) === controller) this.inbound.delete(id);
|
|
193
|
+
};
|
|
159
194
|
return Promise.resolve()
|
|
160
|
-
.then(() => handler(method, message.params, id))
|
|
195
|
+
.then(() => handler(method, message.params, id, controller.signal))
|
|
161
196
|
.then(
|
|
162
197
|
(result) => {
|
|
163
|
-
|
|
198
|
+
cleanup();
|
|
199
|
+
if (result === ABANDON || controller.signal.aborted) return;
|
|
164
200
|
respond({ jsonrpc: "2.0", id, result: result ?? {} });
|
|
165
201
|
},
|
|
166
202
|
(error: unknown) => {
|
|
203
|
+
cleanup();
|
|
204
|
+
if (controller.signal.aborted) return;
|
|
167
205
|
const rpcError =
|
|
168
206
|
error instanceof RpcError
|
|
169
207
|
? error
|