@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/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(), sessions: z.number(), inputTokens: z.number(), outputTokens: z.number(),
8
- cacheReadTokens: z.number(), cacheWriteTokens: z.number(), totalTokens: z.number(),
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(), days: z.number(), agent: z.string(), timezone: z.string(),
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(), label: z.string(), source: z.enum(["native", "acp"]),
17
- status: z.enum(["available", "no_data"]), records: z.number(), totalTokens: z.number(), note: z.string(),
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;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IAC5F,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;CACnF,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IAClF,MAAM,EAAE,iBAAiB;IACzB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACnF,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QACvB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACpE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;KACzG,CAAC,CAAC,CAAC,QAAQ,EAAE;IACd,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC9B,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkshell/wire",
3
- "version": "0.2.30",
3
+ "version": "0.2.32",
4
4
  "description": "Wire protocol for LinkShell v2: session model, JSON-RPC methods, end-to-end encryption and the relay client.",
5
5
  "license": "MIT",
6
6
  "repository": {
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
@@ -20,4 +20,5 @@ export * from "./watch.js";
20
20
  export * from "./usage.js";
21
21
  export * from "./side.js";
22
22
  export * from "./rpc-mcp.js";
23
+ export * from "./lsa.js";
23
24
  export * from "./envelopes.js";
@@ -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
- if (!line.trim()) continue;
94
- let message: Record<string, unknown> | Record<string, unknown>[];
95
- try {
96
- message = JSON.parse(line) as typeof message;
97
- } catch {
98
- continue;
99
- }
100
- if (Array.isArray(message)) {
101
- if (!message.length || message.length > 128) {
102
- this.write({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "invalid JSON-RPC batch" } });
103
- continue;
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
- if (result === ABANDON) return;
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