@nyte-ai/plugin 0.0.18 → 0.0.21

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.
@@ -91,6 +91,9 @@ SOFTWARE.
91
91
  - Transcript reconciliation, checkpoint instruction snapshots, provider system
92
92
  updates, and cache warming are ported from the 1.0.0 release commit
93
93
  `a13d35a742c6ef8462812a28fbe1d8c8b7431c32`.
94
+ - Fitting images to model limits (`packages/core/src/agent/images.ts` and
95
+ `packages/host/src/images`) is ported from `@earendil-works/pi-durable`'s
96
+ images at commit `73f8de80bc87021375f8b525292ac07492253002`.
94
97
 
95
98
  License: MIT, with the following notice:
96
99
 
@@ -148,6 +151,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
148
151
  THE SOFTWARE.
149
152
  ```
150
153
 
154
+ ## Photon (`silvia-odwyer/photon`)
155
+
156
+ - Repository: https://github.com/silvia-odwyer/photon
157
+ - Package: `@silvia-odwyer/photon` 0.3.3
158
+ - What: the host decodes, orients, scales and re-encodes images for the model
159
+ with Photon's WebAssembly build.
160
+
161
+ License: Apache License 2.0, reproduced in the appendix below. Copyright 2023
162
+ Silvia O'Dwyer.
163
+
151
164
  ## Pierre
152
165
 
153
166
  - Repository: https://github.com/pierrecomputer/pierre
@@ -561,6 +574,59 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
561
574
  SOFTWARE.
562
575
  ```
563
576
 
577
+ ## snapcn (`snapcndev/snapcn`)
578
+
579
+ - Repository: https://github.com/snapcndev/snapcn
580
+ - What: the launch film's Logo Flicker and Check Cycle scenes
581
+ (`packages/lab/video/src/snap-cn/`) are adapted from snapcn's registry
582
+ components at commit `47ee435f17d85f74abf48b095b40d64fc9616fb0`.
583
+
584
+ License: MIT, with the following notice:
585
+
586
+ ```text
587
+ MIT License
588
+
589
+ Copyright (c) 2026 Sri Nath (snap-cn)
590
+
591
+ Permission is hereby granted, free of charge, to any person obtaining a copy
592
+ of this software and associated documentation files (the "Software"), to deal
593
+ in the Software without restriction, including without limitation the rights
594
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
595
+ copies of the Software, and to permit persons to whom the Software is
596
+ furnished to do so, subject to the following conditions:
597
+
598
+ The above copyright notice and this permission notice shall be included in all
599
+ copies or substantial portions of the Software.
600
+
601
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
602
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
603
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
604
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
605
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
606
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
607
+ SOFTWARE.
608
+ ```
609
+
610
+ ## Naybr, "Copyright Free Gameplay (No Commentary) - Minecraft Survival"
611
+
612
+ - Source: https://www.youtube.com/watch?v=GPsGyv5zTlM, by Naybr (youtube.com/@Naybr)
613
+ - What: the launch film opens on four shots from 0:30 to 2:30 of this video
614
+ (`clip` in `packages/lab/video/src/film/media.ts`). The footage is not in
615
+ the repository; `packages/lab/video/README.md` says how to fetch it.
616
+
617
+ License: Creative Commons Attribution, as published on YouTube. The author
618
+ allows use, modification, redistribution and monetisation, with this credit
619
+ wherever the gameplay is used: "Gameplay – youtube.com/@Naybr".
620
+
621
+ ## Photographs in the launch film
622
+
623
+ - Source: Wikimedia Commons; each file's author and source page are listed in
624
+ `photos` in `packages/lab/video/src/film/media.ts`.
625
+ - What: eleven photographs the launch film cuts between. They are not in the
626
+ repository.
627
+
628
+ License: every one is public domain or CC0. None requires attribution.
629
+
564
630
  ## Design references
565
631
 
566
632
  These credits record design sources. They do not grant a license to the
@@ -0,0 +1,22 @@
1
+ // src/find-on-path.ts
2
+ import { constants } from "node:fs";
3
+ import { access } from "node:fs/promises";
4
+ import { delimiter, join } from "node:path";
5
+ async function executable2(path) {
6
+ try {
7
+ await access(path, constants.X_OK);
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+ async function findOnPath2(name, path) {
14
+ const names = process.platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
15
+ for (const dir of path.split(delimiter))
16
+ for (const candidate of names)
17
+ if (dir !== "" && await executable2(join(dir, candidate)))
18
+ return join(dir, candidate);
19
+ return;
20
+ }
21
+
22
+ export { executable2, findOnPath2 };
@@ -1,7 +1,11 @@
1
+ import {
2
+ executable2,
3
+ findOnPath2
4
+ } from "./billing-4vz9pxyx.js";
5
+
1
6
  // src/acp/registry.ts
2
- import { constants } from "node:fs";
3
- import { access, readFile, realpath } from "node:fs/promises";
4
- import { basename, delimiter, dirname, join } from "node:path";
7
+ import { readFile, realpath } from "node:fs/promises";
8
+ import { basename, dirname, join } from "node:path";
5
9
  import { Type } from "typebox";
6
10
  import { Value } from "typebox/value";
7
11
  var launch = {
@@ -29,22 +33,6 @@ function platformTarget() {
29
33
  const arch = process.arch === "arm64" ? "aarch64" : process.arch === "x64" ? "x86_64" : process.arch;
30
34
  return `${os}-${arch}`;
31
35
  }
32
- async function executable(path) {
33
- try {
34
- await access(path, constants.X_OK);
35
- return true;
36
- } catch {
37
- return false;
38
- }
39
- }
40
- async function findOnPath(name, path) {
41
- const names = process.platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
42
- for (const dir of path.split(delimiter))
43
- for (const candidate of names)
44
- if (dir !== "" && await executable(join(dir, candidate)))
45
- return join(dir, candidate);
46
- return;
47
- }
48
36
  async function resolved(path) {
49
37
  try {
50
38
  return await realpath(path);
@@ -54,7 +42,7 @@ async function resolved(path) {
54
42
  }
55
43
  async function globalBin(spec, path) {
56
44
  const name = PACKAGE.exec(spec)?.[1] ?? spec;
57
- const npx = await findOnPath("npx", path);
45
+ const npx = await findOnPath2("npx", path);
58
46
  if (npx === undefined)
59
47
  return;
60
48
  const bin = dirname(npx);
@@ -79,12 +67,12 @@ async function globalBin(spec, path) {
79
67
  }
80
68
  const linked = join(bin, command);
81
69
  const [from, to] = await Promise.all([resolved(linked), resolved(join(root, target))]);
82
- return from !== undefined && from === to && await executable(linked) ? linked : undefined;
70
+ return from !== undefined && from === to && await executable2(linked) ? linked : undefined;
83
71
  }
84
72
  async function installed(agent, path) {
85
73
  const binary = agent.distribution.binary?.[platformTarget()];
86
74
  if (binary !== undefined) {
87
- const command = await findOnPath(basename(binary.cmd).replace(/\.exe$/, ""), path);
75
+ const command = await findOnPath2(basename(binary.cmd).replace(/\.exe$/, ""), path);
88
76
  if (command !== undefined)
89
77
  return {
90
78
  id: agent.id,
@@ -137,6 +137,8 @@ class ClaudeCodeSession2 {
137
137
  systemPrompt: { type: "preset", preset: "claude_code" },
138
138
  includePartialMessages: true,
139
139
  mcpServers: { [NYTE_SERVER2]: this.#server },
140
+ disallowedTools: ["Bash"],
141
+ allowedTools: ["Grep", "Glob"],
140
142
  permissionMode: "default",
141
143
  canUseTool: async (name, input, { signal, toolUseID, agentID }) => {
142
144
  if (name.startsWith(NYTE_TOOL_PREFIX2))
@@ -164,7 +166,8 @@ class ClaudeCodeSession2 {
164
166
  CLAUDE_AGENT_SDK_CLIENT_APP: "nyte",
165
167
  CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1"
166
168
  },
167
- stderr: (chunk) => this.#stderr = (this.#stderr + chunk).slice(-4096)
169
+ stderr: (chunk) => this.#stderr = (this.#stderr + chunk).slice(-4096),
170
+ pathToClaudeCodeExecutable: launch.claude
168
171
  };
169
172
  if (launch.effort !== undefined)
170
173
  options.effort = launch.effort;
@@ -15,7 +15,7 @@ import { processEnvironment } from "@nyte-ai/core";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "@nyte-ai/plugin",
18
- version: "0.0.18",
18
+ version: "0.0.21",
19
19
  private: false,
20
20
  license: "MIT",
21
21
  repository: {
@@ -195,7 +195,7 @@ var package_default = {
195
195
  scripts: {
196
196
  clean: "rm -rf node_modules .turbo",
197
197
  test: "vitest --run",
198
- typecheck: "tsgo --noEmit",
198
+ typecheck: "tsrs --noEmit",
199
199
  "build:package": "bun ../../scripts/sdk-packages.mjs build plugin"
200
200
  },
201
201
  dependencies: {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AcpProcess2
3
- } from "../../chunks/billing-9mfedmsh.js";
3
+ } from "../../chunks/billing-wqjabjnc.js";
4
4
  import"../../chunks/billing-svrhh5a5.js";
5
5
  import"../../chunks/billing-9nn3yp18.js";
6
6
  export {
@@ -8,11 +8,12 @@ import {
8
8
  import"../../chunks/billing-svrhh5a5.js";
9
9
  import {
10
10
  AcpProcess2
11
- } from "../../chunks/billing-9mfedmsh.js";
11
+ } from "../../chunks/billing-wqjabjnc.js";
12
+ import"../../chunks/billing-4vz9pxyx.js";
12
13
  import {
13
14
  registryEntries2,
14
15
  installedAgents2
15
- } from "../../chunks/billing-k2p1t25n.js";
16
+ } from "../../chunks/billing-hg64tqf1.js";
16
17
  import {
17
18
  messageKey2,
18
19
  userBlocks2,
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  registryEntries2,
3
3
  installedAgents2
4
- } from "../../chunks/billing-k2p1t25n.js";
4
+ } from "../../chunks/billing-hg64tqf1.js";
5
+ import"../../chunks/billing-4vz9pxyx.js";
5
6
  export {
6
7
  installedAgents2 as installedAgents,
7
8
  registryEntries2 as registryEntries
@@ -5,7 +5,10 @@ var parameters = standard(Type.Object({
5
5
  command: Type.String({ description: "The shell command to run." }),
6
6
  description: Type.String({
7
7
  description: "Clear, concise description of what this command does in 5-10 words, in active voice."
8
- })
8
+ }),
9
+ background: Type.Optional(Type.Boolean({
10
+ description: "Start the command and return a job id with its first output instead of waiting for it to exit. Use for servers, watchers, and other long-lived commands, without a trailing &. The command keeps running across turns, and you are told when it exits; read its output or stop it with the job tool. Do not use it to speed up ordinary commands."
11
+ }))
9
12
  }));
10
13
  var bash_description_default = {
11
14
  id: "bash-description",
@@ -1,3 +1,6 @@
1
+ import {
2
+ findOnPath2
3
+ } from "../../chunks/billing-4vz9pxyx.js";
1
4
  import {
2
5
  answerInput2
3
6
  } from "../../chunks/billing-6z2kgtyt.js";
@@ -13,7 +16,7 @@ import {
13
16
  } from "../../chunks/billing-ye3wk5r3.js";
14
17
  import {
15
18
  ClaudeCodeSession2
16
- } from "../../chunks/billing-wmkxkypm.js";
19
+ } from "../../chunks/billing-wbe8w0te.js";
17
20
  import {
18
21
  mirrorArguments3,
19
22
  userBlocks3,
@@ -50,7 +53,6 @@ var CLAUDE_CODE_HAS_ITS_OWN = new Set([
50
53
  "write",
51
54
  "edit",
52
55
  "list",
53
- "bash",
54
56
  "web_search",
55
57
  "question"
56
58
  ]);
@@ -74,7 +76,13 @@ function effortFor(model, reasoning) {
74
76
  const mapped = model.thinkingLevelMap?.[level] ?? level;
75
77
  return EFFORTS.find((effort) => effort === mapped) ?? (level === "minimal" ? "low" : undefined);
76
78
  }
79
+ async function findClaudeCode() {
80
+ await processEnvironment();
81
+ return await findOnPath2("claude", process.env.PATH ?? "");
82
+ }
77
83
  async function claudeCodeConfigured() {
84
+ if (await findClaudeCode() === undefined)
85
+ return false;
78
86
  try {
79
87
  await access(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"));
80
88
  return true;
@@ -92,7 +100,7 @@ function answerOnce(start, model, context, options) {
92
100
  (async () => {
93
101
  try {
94
102
  stream.push({ type: "start", partial: output });
95
- await processEnvironment();
103
+ const claude = await findClaudeCode();
96
104
  for await (const message of start({
97
105
  prompt: flatPrompt2(context.messages),
98
106
  options: {
@@ -106,7 +114,8 @@ function answerOnce(start, model, context, options) {
106
114
  persistSession: false,
107
115
  includePartialMessages: true,
108
116
  thinking: { type: "disabled" },
109
- env: { ...process.env, ...options?.env }
117
+ env: { ...process.env, ...options?.env },
118
+ pathToClaudeCodeExecutable: claude
110
119
  }
111
120
  })) {
112
121
  if (message.type === "stream_event" && message.parent_tool_use_id === null)
@@ -267,7 +276,7 @@ function claudeCodeSdk(start = query) {
267
276
  session?.close();
268
277
  if (prompt.length === 0)
269
278
  throw new Error(NOT_RUNNING);
270
- await processEnvironment();
279
+ const claude = await findClaudeCode();
271
280
  const claudeSession = randomUUID();
272
281
  session = new ClaudeCodeSession2({
273
282
  key,
@@ -285,6 +294,7 @@ function claudeCodeSdk(start = query) {
285
294
  }),
286
295
  tools,
287
296
  query: start,
297
+ claude,
288
298
  approve: binding.approve
289
299
  });
290
300
  sessions.set(id, session);
@@ -13,6 +13,8 @@ interface Launch {
13
13
  readonly history: readonly SessionStoreEntry[];
14
14
  readonly tools: readonly Tool[];
15
15
  readonly query: StartQuery;
16
+ /** The installed `claude` to run; without one the Agent SDK looks for its own. */
17
+ readonly claude: string | undefined;
16
18
  readonly approve: Approve;
17
19
  }
18
20
  /** Decides a call Claude Code asks to run under Nyte's permissions policy, by the tool's own name. */
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ClaudeCodeSession2
3
- } from "../../chunks/billing-wmkxkypm.js";
3
+ } from "../../chunks/billing-wbe8w0te.js";
4
4
  import"../../chunks/billing-ye3wk5r3.js";
5
5
  export {
6
6
  ClaudeCodeSession2 as ClaudeCodeSession
@@ -1,8 +1,9 @@
1
1
  import type { RunEnd } from "@nyte-ai/schema";
2
2
  /**
3
3
  * Tells the user when a run ends in any session this client can see:
4
- * finished, stopped, or failed with the reason. Sessions a parent spawned
5
- * stay quiet, so a delegation does not ring once per child.
4
+ * finished, or failed with the reason. A stopped run stays quiet because
5
+ * someone asked for it, and so do sessions a parent spawned, so a delegation
6
+ * does not ring once per child.
6
7
  *
7
8
  * @category plugins
8
9
  * @since 0.1.0
@@ -10,9 +10,7 @@ var notifications_default = {
10
10
  const { end } = event;
11
11
  if (end.kind === "completed")
12
12
  api.ui.notify({ title, message: "Run finished", level: "info" });
13
- else if (end.kind === "stopped")
14
- api.ui.notify({ title, message: "Run stopped", level: "warning" });
15
- else
13
+ else if (end.kind === "failed")
16
14
  api.ui.notify({ title, message: `Run failed: ${line(end)}`, level: "error" });
17
15
  }
18
16
  }
@@ -1,6 +1,6 @@
1
1
  import { text, thinking } from "@nyte-ai/core";
2
2
  import type { PartState } from "@nyte-ai/core";
3
- import type { Execution, Failure, HeadInfo, Message, MessageSource, SessionInfo, ToolClass, ToolPartState, UserContent } from "@nyte-ai/schema";
3
+ import type { Execution, Failure, HeadInfo, Message, MessageSource, SessionInfo, ToolClass, ToolPartState, UserContent, UserStatus } from "@nyte-ai/schema";
4
4
  /**
5
5
  * A message the user sent, or one a child, job or action delivered for them.
6
6
  *
@@ -92,6 +92,32 @@ export declare function readRawPart(message: PartMessage): RawPart;
92
92
  * @category presentation
93
93
  */
94
94
  export declare function showsReport(message: UserMessage): message is ReportMessage;
95
+ type PendingStatus = Extract<UserStatus, {
96
+ readonly kind: "pending";
97
+ }>;
98
+ /**
99
+ * A message that waits in the queue: unsent, or pending for a later turn. A pending steer sent
100
+ * while idle or joining the live run is never one.
101
+ *
102
+ * @category presentation
103
+ */
104
+ export type QueuedMessage = UserMessage & {
105
+ readonly status: Extract<UserStatus, {
106
+ readonly kind: "unsent";
107
+ }> | (PendingStatus & {
108
+ readonly schedule: Exclude<PendingStatus["schedule"], {
109
+ readonly kind: "joined";
110
+ }>;
111
+ });
112
+ };
113
+ /**
114
+ * Whether a message waits in the queue rather than showing in the transcript. A retrying send
115
+ * always waits. Reports, joined steers and steers sent while idle stay in the transcript.
116
+ * Other messages wait only while a run is active.
117
+ *
118
+ * @category presentation
119
+ */
120
+ export declare function waitsInQueue(message: UserMessage, running: boolean): message is QueuedMessage;
95
121
  /**
96
122
  * What a user message shows: an action's label in place of the prompt it sent.
97
123
  *
@@ -21,6 +21,23 @@ function readRawPart(message) {
21
21
  function showsReport(message) {
22
22
  return message.source.kind === "child" || message.source.kind === "job";
23
23
  }
24
+ function waitsInQueue(message, running) {
25
+ const { status } = message;
26
+ if (showsReport(message))
27
+ return false;
28
+ switch (status.kind) {
29
+ case "landed":
30
+ return false;
31
+ case "unsent":
32
+ return status.retrying !== null || running && message.delivery !== "steer";
33
+ case "pending":
34
+ return running && status.schedule.kind !== "joined" && !(message.delivery === "steer" && status.schedule.kind === "next-turn" && status.schedule.reason === "idle");
35
+ default: {
36
+ const _exhaustive = status;
37
+ return _exhaustive;
38
+ }
39
+ }
40
+ }
24
41
  function shownContent(message) {
25
42
  return message.source.kind === "action" ? message.source.label : message.content;
26
43
  }
@@ -369,5 +386,6 @@ export {
369
386
  toolStatus,
370
387
  toolSubject,
371
388
  toolTitle,
372
- toolVerbs
389
+ toolVerbs,
390
+ waitsInQueue
373
391
  };
@@ -0,0 +1,4 @@
1
+ /** Whether `path` is a file this process may run. */
2
+ export declare function executable(path: string): Promise<boolean>;
3
+ /** The first executable `name` in the directories of `path`, a `PATH`-style list. */
4
+ export declare function findOnPath(name: string, path: string): Promise<string | undefined>;
@@ -0,0 +1,8 @@
1
+ import {
2
+ executable2,
3
+ findOnPath2
4
+ } from "../chunks/billing-4vz9pxyx.js";
5
+ export {
6
+ executable2 as executable,
7
+ findOnPath2 as findOnPath
8
+ };
@@ -8,12 +8,14 @@ import type { Command } from "@nyte-ai/plugin/core";
8
8
  */
9
9
  export declare const TITLE_PROMPT = "You write the title of a conversation so the user can find it later.\nAnswer with one line of at most 50 characters and nothing else, in the language of the user's request.\nName the topic or task, keep technical terms, numbers and file names exact, and never answer the request itself.";
10
10
  /**
11
- * Names an unnamed root session after its first run: one `api.request` to
12
- * the session's model for a title, then `session.update({ name })`. A named
13
- * session or a child keeps its name, so every later run is a no-op. The
14
- * request runs past the hook's budget, bounded by the plugin's deactivation;
15
- * a failure is reported and leaves the name alone. `/rename <name>` names the
16
- * chat; `/rename` alone asks the model again.
11
+ * Names an unnamed root session as its first run starts: the first line of
12
+ * the user's message at once, then the title one `api.request` to the
13
+ * session's model returns, alongside the run. The title replaces only that
14
+ * first line, so a name given meanwhile stays. A named session or a child
15
+ * keeps its name, so every later run is a no-op. The request runs past the
16
+ * hook's budget, bounded by the plugin's deactivation; a failure is reported
17
+ * and leaves the first line as the name. `/rename <name>` names the chat;
18
+ * `/rename` alone asks the model again.
17
19
  *
18
20
  * @category plugins
19
21
  * @since 0.1.0
@@ -24,31 +26,30 @@ declare const _default: {
24
26
  commands: Command<"core">[];
25
27
  };
26
28
  hooks: {
27
- run_ended: (_end: Readonly<{
28
- readonly kind: "completed";
29
- readonly usage: {
30
- readonly output: number;
31
- readonly input: number;
32
- readonly cacheRead: number;
33
- readonly cacheWrite: number;
34
- readonly reasoning: number | null;
35
- readonly total: number;
36
- readonly cost: {
37
- readonly output: number;
38
- readonly input: number;
39
- readonly cacheRead: number;
40
- readonly cacheWrite: number;
41
- readonly total: number;
29
+ run_started: (run: Readonly<{
30
+ readonly configuration: {
31
+ readonly model: {
32
+ readonly id: string;
33
+ readonly provider: string;
42
34
  };
35
+ readonly thinking: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
36
+ readonly kind: "exact";
37
+ readonly settings: {
38
+ readonly [x: string]: import("@nyte-ai/schema").Json;
39
+ };
40
+ readonly agent: string | null;
41
+ readonly settingsVersion: string;
43
42
  };
44
- } | {
45
- readonly kind: "stopped";
46
- } | {
47
- readonly kind: "failed";
48
- readonly failure: {
49
- readonly retryAfterMs?: number | undefined;
50
- readonly message: string;
51
- readonly class: "aborted" | "provider" | "rate_limit" | "auth" | "quota" | "context_window" | "overloaded" | "network" | "runner";
43
+ readonly id: import("@nyte-ai/schema").Id<"run">;
44
+ readonly head: import("@nyte-ai/schema").Id<"head">;
45
+ readonly startedAt: number;
46
+ readonly attempt: number;
47
+ readonly origin: {
48
+ readonly message: import("@nyte-ai/schema").Id<"message">;
49
+ readonly kind: "input";
50
+ } | {
51
+ readonly kind: "continuation";
52
+ readonly from: "child" | "job" | "wake";
52
53
  };
53
54
  }>, { api }: {
54
55
  readonly api: {
@@ -19,10 +19,12 @@ var rename_default = {
19
19
  id: "rename",
20
20
  contribute: { commands: [renameCommand] },
21
21
  hooks: {
22
- run_ended: (_end, { api }) => {
22
+ run_started: (run, { api }) => {
23
+ if (run.origin.kind !== "input")
24
+ return;
23
25
  nameUnnamed(api).catch((error) => api.report({
24
26
  severity: "warning",
25
- path: ["run_ended"],
27
+ path: ["run_started"],
26
28
  message: `Naming the session failed: ${error instanceof Error ? error.message : String(error)}`,
27
29
  source: { plugin: "rename" }
28
30
  }));
@@ -33,7 +35,17 @@ async function nameUnnamed(api) {
33
35
  const info = await api.session.info();
34
36
  if (info.name !== null || info.parent !== null)
35
37
  return;
36
- await api.session.update({ name: await requestTitle(api, api.signal) });
38
+ const view = await api.session.view();
39
+ const words = view.messages.flatMap((message) => message.kind === "user" ? [userWords(message.content)] : [])[0];
40
+ const firstLine = words?.split(`
41
+ `).find((line) => line.trim() !== "")?.trim().slice(0, NAME_LIMIT);
42
+ if (firstLine === undefined)
43
+ return;
44
+ await api.session.update({ name: firstLine });
45
+ const title = await requestTitle(api, api.signal);
46
+ if ((await api.session.info()).name !== firstLine)
47
+ return;
48
+ await api.session.update({ name: title });
37
49
  }
38
50
  async function requestTitle(api, signal) {
39
51
  const view = await api.session.view();
@@ -63,15 +75,16 @@ async function requestTitle(api, signal) {
63
75
  }
64
76
  function transcript(messages) {
65
77
  return messages.flatMap((message) => {
66
- if (message.kind === "user") {
67
- const words = typeof message.content === "string" ? message.content : message.content.flatMap((block) => block.kind === "text" ? [block.text] : []).join(" ");
68
- return [`User: ${words}`];
69
- }
78
+ if (message.kind === "user")
79
+ return [`User: ${userWords(message.content)}`];
70
80
  return isPart(message, text) ? [`Assistant: ${message.state.markdown}`] : [];
71
81
  }).join(`
72
82
 
73
83
  `).slice(0, 4000);
74
84
  }
85
+ function userWords(content) {
86
+ return typeof content === "string" ? content : content.flatMap((block) => block.kind === "text" ? [block.text] : []).join(" ");
87
+ }
75
88
  export {
76
89
  TITLE_PROMPT,
77
90
  rename_default as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyte-ai/plugin",
3
- "version": "0.0.18",
3
+ "version": "0.0.21",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -148,9 +148,9 @@
148
148
  "@agentclientprotocol/sdk": "1.9.0",
149
149
  "@anthropic-ai/claude-agent-sdk": "0.3.296",
150
150
  "@earendil-works/pi-codemode": "1.0.0",
151
- "@nyte-ai/ai": "0.0.18",
152
- "@nyte-ai/core": "0.0.18",
153
- "@nyte-ai/schema": "0.0.18",
151
+ "@nyte-ai/ai": "0.0.21",
152
+ "@nyte-ai/core": "0.0.21",
153
+ "@nyte-ai/schema": "0.0.21",
154
154
  "@opentelemetry/api": "^1.9.1",
155
155
  "@opentelemetry/exporter-trace-otlp-http": "^0.223.0",
156
156
  "@opentelemetry/resources": "^2.12.0",
@@ -187,7 +187,7 @@
187
187
  "scripts": {
188
188
  "clean": "rm -rf node_modules .turbo",
189
189
  "test": "vitest --run",
190
- "typecheck": "tsgo --noEmit",
190
+ "typecheck": "tsrs --noEmit",
191
191
  "build:package": "bun ../../scripts/sdk-packages.mjs build plugin"
192
192
  }
193
193
  }