@cjhyy/code-shell-pet 0.8.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.
Files changed (74) hide show
  1. package/README.md +99 -0
  2. package/dist/capability.d.ts +15 -0
  3. package/dist/capability.js +251 -0
  4. package/dist/current-time.d.ts +5 -0
  5. package/dist/current-time.js +43 -0
  6. package/dist/delegate-work.d.ts +7 -0
  7. package/dist/delegate-work.js +142 -0
  8. package/dist/delegation.d.ts +33 -0
  9. package/dist/delegation.js +6 -0
  10. package/dist/disclosure/catalog.d.ts +22 -0
  11. package/dist/disclosure/catalog.js +104 -0
  12. package/dist/disclosure/constants.d.ts +7 -0
  13. package/dist/disclosure/constants.js +7 -0
  14. package/dist/disclosure/jsonl.d.ts +9 -0
  15. package/dist/disclosure/jsonl.js +61 -0
  16. package/dist/disclosure/latest-result.d.ts +8 -0
  17. package/dist/disclosure/latest-result.js +35 -0
  18. package/dist/disclosure/search.d.ts +21 -0
  19. package/dist/disclosure/search.js +124 -0
  20. package/dist/disclosure/selector.d.ts +2 -0
  21. package/dist/disclosure/selector.js +5 -0
  22. package/dist/disclosure/todo-snapshot.d.ts +8 -0
  23. package/dist/disclosure/todo-snapshot.js +54 -0
  24. package/dist/follow-ups.d.ts +21 -0
  25. package/dist/follow-ups.js +102 -0
  26. package/dist/gateway.d.ts +41 -0
  27. package/dist/gateway.js +263 -0
  28. package/dist/host-actions.d.ts +51 -0
  29. package/dist/host-actions.js +405 -0
  30. package/dist/index.capability.d.ts +7 -0
  31. package/dist/index.capability.js +7 -0
  32. package/dist/index.d.ts +32 -0
  33. package/dist/index.disclosure.d.ts +13 -0
  34. package/dist/index.disclosure.js +13 -0
  35. package/dist/index.js +32 -0
  36. package/dist/index.protocol.d.ts +8 -0
  37. package/dist/index.protocol.js +8 -0
  38. package/dist/index.team.d.ts +2 -0
  39. package/dist/index.team.js +2 -0
  40. package/dist/long-task.d.ts +251 -0
  41. package/dist/long-task.js +795 -0
  42. package/dist/mobile-remote.d.ts +14 -0
  43. package/dist/mobile-remote.js +39 -0
  44. package/dist/outbound-message.d.ts +15 -0
  45. package/dist/outbound-message.js +127 -0
  46. package/dist/pending-decision-index.d.ts +24 -0
  47. package/dist/pending-decision-index.js +110 -0
  48. package/dist/profile.d.ts +27 -0
  49. package/dist/profile.js +120 -0
  50. package/dist/projection-extension.d.ts +8 -0
  51. package/dist/projection-extension.js +238 -0
  52. package/dist/protocol.d.ts +58 -0
  53. package/dist/protocol.js +15 -0
  54. package/dist/report-to-mimi.d.ts +7 -0
  55. package/dist/report-to-mimi.js +87 -0
  56. package/dist/run-params.d.ts +34 -0
  57. package/dist/run-params.js +387 -0
  58. package/dist/segment-closure.d.ts +52 -0
  59. package/dist/segment-closure.js +130 -0
  60. package/dist/session-control.d.ts +5 -0
  61. package/dist/session-control.js +46 -0
  62. package/dist/session-index.d.ts +22 -0
  63. package/dist/session-index.js +324 -0
  64. package/dist/sessions-tool.d.ts +21 -0
  65. package/dist/sessions-tool.js +145 -0
  66. package/dist/team.d.ts +13 -0
  67. package/dist/team.js +38 -0
  68. package/dist/tool-arguments.d.ts +1 -0
  69. package/dist/tool-arguments.js +10 -0
  70. package/dist/topic-segment.d.ts +55 -0
  71. package/dist/topic-segment.js +37 -0
  72. package/dist/types.d.ts +95 -0
  73. package/dist/types.js +1 -0
  74. package/package.json +56 -0
@@ -0,0 +1,263 @@
1
+ import { hasOnlyDeclaredToolArguments } from "./tool-arguments.js";
2
+ export const GATEWAY_TOOL_NAME = "Gateway";
3
+ export const gatewayToolDef = {
4
+ name: GATEWAY_TOOL_NAME,
5
+ description: "First-level, read-only progressive discovery for Chat Gateway. Search only the channels " +
6
+ "granted to this turn, or describe the exact inbound/outbound contract of one channel. " +
7
+ "Use GatewayReply as the second-level execution tool for the current originating conversation.",
8
+ inputSchema: {
9
+ type: "object",
10
+ additionalProperties: false,
11
+ properties: {
12
+ action: {
13
+ type: "string",
14
+ enum: ["search", "describe"],
15
+ description: "search returns compact channel matches; describe returns one exact channel contract.",
16
+ },
17
+ query: {
18
+ type: "string",
19
+ minLength: 1,
20
+ maxLength: 128,
21
+ description: "Optional search terms. Omit to list every granted channel. Use terms such as " +
22
+ "channel:telegram, inbound:file, outbound:image, outbound:proactive, " +
23
+ "outbound:direct, button:native, or current.",
24
+ },
25
+ channel: {
26
+ type: "string",
27
+ minLength: 1,
28
+ maxLength: 32,
29
+ description: "Granted channel name for describe. Omit it to inspect the current originating channel.",
30
+ },
31
+ },
32
+ required: ["action"],
33
+ },
34
+ };
35
+ export function gatewayAvailability(ctx) {
36
+ return Boolean(ctx.profileMeta?.petGateway);
37
+ }
38
+ export async function gatewayTool(args, ctx) {
39
+ const catalog = ctx?.runScopedServices
40
+ ?.petGateway;
41
+ if (!catalog)
42
+ return "Error: Gateway is available only in a Mimi turn with Gateway context.";
43
+ if (!hasOnlyDeclaredToolArguments(args, ["action", "query", "channel"]) ||
44
+ typeof args.action !== "string") {
45
+ return "Error: Gateway requires an action and accepts only query or channel.";
46
+ }
47
+ if (args.action === "search") {
48
+ if (args.channel !== undefined) {
49
+ return "Error: Gateway search does not accept channel.";
50
+ }
51
+ if (args.query !== undefined &&
52
+ (typeof args.query !== "string" ||
53
+ args.query.length < 1 ||
54
+ args.query.length > 128 ||
55
+ args.query.trim().length < 1)) {
56
+ return "Error: Gateway search query must be 1 to 128 non-blank characters.";
57
+ }
58
+ const query = typeof args.query === "string" ? args.query : undefined;
59
+ return JSON.stringify({
60
+ currentChannel: catalog.currentChannel,
61
+ matches: catalog.channels
62
+ .filter((entry) => matchesGatewayQuery(entry, catalog.currentChannel, query))
63
+ .map(({ channel }) => ({
64
+ channel,
65
+ current: channel === catalog.currentChannel,
66
+ })),
67
+ next: `Call ${GATEWAY_TOOL_NAME} with action=describe and an optional matched channel, then use GatewayReply for the current route.`,
68
+ });
69
+ }
70
+ if (args.action !== "describe") {
71
+ return "Error: Gateway action must be search or describe.";
72
+ }
73
+ if (args.query !== undefined) {
74
+ return "Error: Gateway describe does not accept query.";
75
+ }
76
+ if (args.channel !== undefined &&
77
+ (typeof args.channel !== "string" || !isChannelName(args.channel))) {
78
+ return "Error: Gateway channel must be a configured channel name.";
79
+ }
80
+ const requested = typeof args.channel === "string" ? args.channel.toLowerCase() : catalog.currentChannel;
81
+ const selected = catalog.channels.find(({ channel }) => channel === requested);
82
+ if (!selected) {
83
+ return `Error: channel ${requested} is not granted to this Gateway turn. Call search first.`;
84
+ }
85
+ return JSON.stringify({
86
+ current: selected.channel === catalog.currentChannel,
87
+ channel: selected.channel,
88
+ capabilities: selected.capabilities,
89
+ execution: selected.channel === catalog.currentChannel
90
+ ? {
91
+ tool: "GatewayReply",
92
+ destination: "current originating IM conversation",
93
+ }
94
+ : {
95
+ tool: null,
96
+ reason: "GatewayReply is intentionally bound to the current originating conversation.",
97
+ },
98
+ });
99
+ }
100
+ function matchesGatewayQuery(entry, currentChannel, query) {
101
+ if (query === undefined)
102
+ return true;
103
+ const terms = query.toLowerCase().trim().split(/\s+/u);
104
+ return terms.every((term) => matchesGatewayTerm(entry, currentChannel, term));
105
+ }
106
+ function matchesGatewayTerm(entry, currentChannel, term) {
107
+ if (term === "current")
108
+ return entry.channel === currentChannel;
109
+ const [scope, value, extra] = term.split(":");
110
+ if (extra !== undefined || value === "")
111
+ return false;
112
+ if (value !== undefined) {
113
+ if (scope === "channel")
114
+ return entry.channel.includes(value);
115
+ if (scope === "button")
116
+ return entry.capabilities.outbound.button === value;
117
+ if (scope === "inbound") {
118
+ return (value === "text" || entry.capabilities.inbound.attachments.includes(asAttachment(value)));
119
+ }
120
+ if (scope === "outbound") {
121
+ return (value === "text" ||
122
+ (value === "proactive" && entry.capabilities.outbound.proactive === true) ||
123
+ (value === "direct" && entry.capabilities.outbound.direct === true) ||
124
+ entry.capabilities.outbound.attachments.includes(asAttachment(value)));
125
+ }
126
+ return false;
127
+ }
128
+ if (entry.channel.includes(term))
129
+ return true;
130
+ if (entry.capabilities.outbound.button === term)
131
+ return true;
132
+ if (term === "text")
133
+ return true;
134
+ if (term === "proactive")
135
+ return entry.capabilities.outbound.proactive === true;
136
+ if (term === "direct")
137
+ return entry.capabilities.outbound.direct === true;
138
+ const attachment = asAttachment(term);
139
+ return (entry.capabilities.inbound.attachments.includes(attachment) ||
140
+ entry.capabilities.outbound.attachments.includes(attachment));
141
+ }
142
+ function asAttachment(value) {
143
+ return value;
144
+ }
145
+ export function parsePetGatewayCatalog(value) {
146
+ if (!isRecord(value) || !hasExactKeys(value, ["currentChannel", "channels"]))
147
+ return undefined;
148
+ if (!isChannelName(value.currentChannel) || !Array.isArray(value.channels))
149
+ return undefined;
150
+ if (value.channels.length < 1 || value.channels.length > 32)
151
+ return undefined;
152
+ const channels = [];
153
+ const names = new Set();
154
+ for (const raw of value.channels) {
155
+ if (!isRecord(raw) || !hasExactKeys(raw, ["channel", "capabilities"]))
156
+ return undefined;
157
+ if (!isChannelName(raw.channel) || names.has(raw.channel))
158
+ return undefined;
159
+ const capabilities = parseCapabilities(raw.capabilities);
160
+ if (!capabilities)
161
+ return undefined;
162
+ names.add(raw.channel);
163
+ channels.push(Object.freeze({ channel: raw.channel, capabilities }));
164
+ }
165
+ if (!names.has(value.currentChannel))
166
+ return undefined;
167
+ return Object.freeze({
168
+ currentChannel: value.currentChannel,
169
+ channels: Object.freeze(channels),
170
+ });
171
+ }
172
+ function parseCapabilities(value) {
173
+ if (!isRecord(value) || !hasExactKeys(value, ["inbound", "outbound"]))
174
+ return undefined;
175
+ if (!isRecord(value.inbound) ||
176
+ !hasExactKeys(value.inbound, ["text", "attachments"]) ||
177
+ value.inbound.text !== true) {
178
+ return undefined;
179
+ }
180
+ const inboundAttachments = parseAttachmentKinds(value.inbound.attachments);
181
+ if (!inboundAttachments || !isRecord(value.outbound) || value.outbound.text !== true) {
182
+ return undefined;
183
+ }
184
+ if (Object.keys(value.outbound).some((key) => ![
185
+ "text",
186
+ // `proactive` is part of the chat package's capability shape. It was
187
+ // missing from this allowlist, and the check rejects ANY unknown key —
188
+ // so every real channel failed catalog validation and Mimi silently
189
+ // lost Gateway discovery on IM turns.
190
+ "proactive",
191
+ "direct",
192
+ "maxTextLength",
193
+ "button",
194
+ "attachments",
195
+ "maxAttachments",
196
+ "maxAttachmentBytes",
197
+ ].includes(key)) ||
198
+ (value.outbound.button !== "native" && value.outbound.button !== "link")) {
199
+ return undefined;
200
+ }
201
+ const outboundAttachments = parseAttachmentKinds(value.outbound.attachments);
202
+ if (!outboundAttachments)
203
+ return undefined;
204
+ const maxTextLength = optionalBoundedInteger(value.outbound.maxTextLength, 1, 8_000);
205
+ const maxAttachments = optionalBoundedInteger(value.outbound.maxAttachments, 1, 4);
206
+ const maxAttachmentBytes = optionalBoundedInteger(value.outbound.maxAttachmentBytes, 1, 10 * 1024 * 1024);
207
+ if (maxTextLength === null || maxAttachments === null || maxAttachmentBytes === null) {
208
+ return undefined;
209
+ }
210
+ // Must be a real boolean when present: the value reaches Mimi's prompt as a
211
+ // capability claim, so a truthy string would misreport what the channel can do.
212
+ const proactive = value.outbound.proactive;
213
+ if (proactive !== undefined && typeof proactive !== "boolean")
214
+ return undefined;
215
+ const direct = value.outbound.direct;
216
+ if (direct !== undefined && typeof direct !== "boolean")
217
+ return undefined;
218
+ return Object.freeze({
219
+ inbound: Object.freeze({ text: true, attachments: inboundAttachments }),
220
+ outbound: Object.freeze({
221
+ text: true,
222
+ // Carried through rather than dropped — "can push unprompted" is exactly
223
+ // the kind of thing Mimi must not assume.
224
+ ...(proactive === undefined ? {} : { proactive }),
225
+ ...(direct === undefined ? {} : { direct }),
226
+ ...(maxTextLength === undefined ? {} : { maxTextLength }),
227
+ button: value.outbound.button,
228
+ attachments: outboundAttachments,
229
+ ...(maxAttachments === undefined ? {} : { maxAttachments }),
230
+ ...(maxAttachmentBytes === undefined ? {} : { maxAttachmentBytes }),
231
+ }),
232
+ });
233
+ }
234
+ function parseAttachmentKinds(value) {
235
+ if (!Array.isArray(value) ||
236
+ value.length > 4 ||
237
+ !value.every((kind) => ["image", "file", "audio", "video"].includes(String(kind))) ||
238
+ new Set(value).size !== value.length) {
239
+ return undefined;
240
+ }
241
+ return Object.freeze([...value]);
242
+ }
243
+ function optionalBoundedInteger(value, minimum, maximum) {
244
+ if (value === undefined)
245
+ return undefined;
246
+ return Number.isSafeInteger(value) && Number(value) >= minimum && Number(value) <= maximum
247
+ ? Number(value)
248
+ : null;
249
+ }
250
+ function isRecord(value) {
251
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
252
+ }
253
+ function hasExactKeys(record, expected) {
254
+ const actual = Object.keys(record).sort();
255
+ const sorted = [...expected].sort();
256
+ return actual.length === sorted.length && actual.every((key, index) => key === sorted[index]);
257
+ }
258
+ function isChannelName(value) {
259
+ return (typeof value === "string" &&
260
+ value.length > 0 &&
261
+ value.length <= 32 &&
262
+ /^[a-z0-9][a-z0-9_-]*$/u.test(value));
263
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Generic "host action" envelope: a Mimi tool records a validated structured
3
+ * request during her turn, and the host executes it only after the turn ends,
4
+ * folding the real outcome back into the reply. One convention shared by every
5
+ * atomic CodeShell capability exposed to Mimi (mobile remote, long-task
6
+ * control, memory, ...), so adding a capability means one tool + one host
7
+ * executor instead of a bespoke wire per feature.
8
+ */
9
+ import type { ToolContext, ToolDefinition, ToolVisibilityContext } from "@cjhyy/code-shell-core/extension";
10
+ export declare const PET_HOST_ACTION_KINDS: readonly ["mobileRemote", "longTaskControl", "memory", "gatewayReply", "followUpMutation", "sessionArchive", "outboundMessage"];
11
+ export type PetHostActionKind = (typeof PET_HOST_ACTION_KINDS)[number];
12
+ export declare function isPetHostActionKind(value: unknown): value is PetHostActionKind;
13
+ export interface PetHostActionRequest {
14
+ kind: PetHostActionKind;
15
+ payload: Record<string, unknown>;
16
+ }
17
+ export interface PetHostActionDecision {
18
+ ok: boolean;
19
+ error?: string;
20
+ }
21
+ export type PetHostActionService = (request: PetHostActionRequest) => PetHostActionDecision;
22
+ export type PetGatewayReplyAttachmentKind = "image" | "file" | "audio" | "video";
23
+ /** Trusted per-route limits used to rewrite and execute the GatewayReply tool. */
24
+ export interface PetGatewayReplyCapability {
25
+ button: "native" | "link";
26
+ attachments: readonly PetGatewayReplyAttachmentKind[];
27
+ maxTextLength: number;
28
+ maxAttachments: number;
29
+ maxAttachmentBytes: number;
30
+ }
31
+ /**
32
+ * Validate the full worker-to-host envelope, including each kind's payload.
33
+ * Tool input schemas are not a trust boundary: the worker response still
34
+ * crosses a process boundary and must fail closed before a host side effect.
35
+ */
36
+ export declare function isPetHostActionRequest(value: unknown): value is PetHostActionRequest;
37
+ export declare function hostActionAvailability(kind: PetHostActionKind): (ctx: ToolVisibilityContext) => boolean;
38
+ export declare function hostActionService(ctx?: ToolContext): PetHostActionService | undefined;
39
+ export declare const GATEWAY_REPLY_TOOL_NAME = "GatewayReply";
40
+ export declare const gatewayReplyToolDef: ToolDefinition;
41
+ export declare const gatewayReplyAvailability: (ctx: ToolVisibilityContext) => boolean;
42
+ export declare function rewriteGatewayReplyDef(def: ToolDefinition, ctx: ToolVisibilityContext): ToolDefinition;
43
+ export declare function gatewayReplyTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
44
+ export declare const CONTROL_LONG_TASK_TOOL_NAME = "ControlLongTask";
45
+ export declare const controlLongTaskToolDef: ToolDefinition;
46
+ export declare const controlLongTaskAvailability: (ctx: ToolVisibilityContext) => boolean;
47
+ export declare function controlLongTaskTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
48
+ export declare const MEMORY_TOOL_NAME = "Memory";
49
+ export declare const memoryToolDef: ToolDefinition;
50
+ export declare const memoryAvailability: (ctx: ToolVisibilityContext) => boolean;
51
+ export declare function memoryTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;