openshain 0.2.0 → 0.3.1

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/bin.js CHANGED
@@ -1,14 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { createInterface } from "node:readline/promises";
3
2
  import { parseArgs } from "node:util";
4
3
  import { anthropicProvider, openaiCompatibleProvider } from "@openshain/agent";
5
4
  import { isOpenshainError } from "@openshain/core";
6
5
  import { standardTools } from "@openshain/tools";
7
6
  import { init } from "./commands/init.js";
8
7
  import { mcp } from "./commands/mcp.js";
9
- import { run } from "./commands/run.js";
10
8
  import { toolsList } from "./commands/tools.js";
11
- import { workList, workResume, workShow } from "./commands/work.js";
9
+ import { workList, workShow } from "./commands/work.js";
12
10
  import { plain } from "./format.js";
13
11
  import { errorLabel } from "./labels.js";
14
12
  import { startTui } from "./tui/index.js";
@@ -16,11 +14,9 @@ import { findWorkspace } from "./workspace.js";
16
14
  const USAGE = `使い方:
17
15
  openshain 端末で対話を始める
18
16
  openshain init openshain.yaml のひな型を書く
19
- openshain run "<依頼>" 依頼を Work として進める
20
17
  openshain tools list 使える Tool の一覧
21
18
  openshain work list Work の一覧
22
19
  openshain work show <id> Work の詳細
23
- openshain work resume <id> 途中で止まった Work を続ける
24
20
  openshain mcp MCP Server を stdio で起動する
25
21
 
26
22
  --workspace <dir> 起点のディレクトリ。省略時はカレントディレクトリ
@@ -65,15 +61,6 @@ async function main(argv) {
65
61
  case "init":
66
62
  await init({ workspaceRoot: values.workspace ?? process.cwd(), write });
67
63
  return 0;
68
- case "run": {
69
- const objective = rest.join(" ").trim();
70
- if (!objective) {
71
- write('依頼の文を指定してください。openshain run "今月の経理を進めて" のように。');
72
- return 2;
73
- }
74
- const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
75
- return withTerminal((ask) => run({ workspaceRoot, providers, objective, write, ...(ask && { ask }) }));
76
- }
77
64
  case "mcp": {
78
65
  const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
79
66
  await mcp({ workspaceRoot, providers });
@@ -91,7 +78,7 @@ async function main(argv) {
91
78
  case "work": {
92
79
  const sub = rest[0];
93
80
  const id = rest[1] ?? "";
94
- if (!(sub === "list" || ((sub === "show" || sub === "resume") && id))) {
81
+ if (!(sub === "list" || (sub === "show" && id))) {
95
82
  write(USAGE);
96
83
  return 2;
97
84
  }
@@ -100,11 +87,8 @@ async function main(argv) {
100
87
  await workList({ workspaceRoot, write });
101
88
  return 0;
102
89
  }
103
- if (sub === "show") {
104
- await workShow({ workspaceRoot, id, write });
105
- return 0;
106
- }
107
- return withTerminal((ask) => workResume({ workspaceRoot, providers, id, write, ...(ask && { ask }) }));
90
+ await workShow({ workspaceRoot, id, write });
91
+ return 0;
108
92
  }
109
93
  default:
110
94
  write(`不明なコマンド ${command}`);
@@ -112,19 +96,6 @@ async function main(argv) {
112
96
  return 2;
113
97
  }
114
98
  }
115
- /** Runs `fn` with a way to ask the person when a terminal is there to answer; otherwise without one. */
116
- async function withTerminal(fn) {
117
- // Without a terminal there is no one to answer; the work waits instead.
118
- if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
119
- return fn();
120
- const rl = createInterface({ input: process.stdin, output: process.stdout });
121
- try {
122
- return await fn((question) => rl.question(`${plain(question)}\n> `));
123
- }
124
- finally {
125
- rl.close();
126
- }
127
- }
128
99
  main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
129
100
  if (isOpenshainError(err)) {
130
101
  const heading = errorLabel(err.code);
@@ -6,7 +6,7 @@ export declare const CONFIG_TEMPLATE: string;
6
6
  /** Registers the runtime as a project MCP server for Claude Code. `openshain` must be on PATH. */
7
7
  export declare const MCP_TEMPLATE: string;
8
8
  /** What an outside agent reads before working in the folder. Codex reads AGENTS.md; Claude Code reads it through CLAUDE.md. */
9
- export declare const AGENTS_TEMPLATE = "# \u3053\u306E\u4F1A\u793E\u30D5\u30A9\u30EB\u30C0\u3067\u50CD\u304F\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\n\n\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u306F openshain \u306E Company Workspace \u3067\u3059\u3002\u3053\u306E\u6307\u793A\u306F\u3001Claude Code \u3084 Codex \u306E\u3088\u3046\u306A\u5916\u90E8\u306E\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u304C MCP \u7D4C\u7531\u3067\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u6271\u3046\u3068\u304D\u306E\u3082\u306E\u3067\u3059\u3002`openshain run` \u3067 Runtime \u81EA\u8EAB\u304C\u52D5\u304F\u3068\u304D\u306F\u3001Work \u306E\u4F5C\u6210\u3068\u5B8C\u4E86\u3092 Runtime \u304C\u884C\u3046\u306E\u3067\u3001\u4E0B\u306E work_* \u306E\u624B\u9806\u306F\u5F53\u3066\u306F\u307E\u308A\u307E\u305B\u3093\u3002\n\n\u4F1A\u793E\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u66F8\u304D\u3068\u96C6\u8A08\u306F openshain \u306E MCP tool \u3067\u884C\u3044\u307E\u3059\u3002Claude Code \u3084 Codex \u81EA\u8EAB\u306E Read\u3001Write\u3001Bash \u306F\u4F1A\u793E\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u306F\u4F7F\u3044\u307E\u305B\u3093\u3002Runtime \u3092\u901A\u3089\u306A\u304B\u3063\u305F\u64CD\u4F5C\u306F\u8A18\u9332\u306B\u6B8B\u3089\u306A\u3044\u305F\u3081\u3067\u3059\u3002\n\n- \u4F9D\u983C\u3092\u53D7\u3051\u305F\u3089\u3001\u307E\u305A `work_create` \u306B\u4F9D\u983C\u306E\u6587\u3092\u305D\u306E\u307E\u307E\u6E21\u3057\u3066 Work \u3092\u4F5C\u308B\n- \u30D5\u30A1\u30A4\u30EB\u306F `fs_list`\u3001`fs_search`\u3001`fs_read`\u3001`csv_read`\u3001`markdown_read` \u3067\u898B\u308B\u3002\u5408\u8A08\u3084\u4EF6\u6570\u306F `csv_aggregate` \u306B\u4EFB\u305B\u3001\u81EA\u5206\u3067\u5408\u8A08\u3057\u306A\u3044\n- \u66F8\u304F\u3068\u304D\u306F `fs_write` \u304B `csv_write`\n- \u7D42\u308F\u3063\u305F\u3089 `work_complete` \u306B\u3001\u4F55\u3092\u3057\u305F\u304B\u3068\u3001\u66F8\u3044\u305F\u30D5\u30A1\u30A4\u30EB\u3092\u6E21\u3059\u3002\u7D9A\u3051\u3089\u308C\u306A\u3044\u3068\u304D\u306F `work_fail`\n- `openshain.yaml` \u3068 `work/` \u306F Runtime \u306E\u3082\u306E\u3002\u5909\u66F4\u3057\u306A\u3044\n";
9
+ export declare const AGENTS_TEMPLATE = "# \u3053\u306E\u4F1A\u793E\u30D5\u30A9\u30EB\u30C0\u3067\u50CD\u304F\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\n\n\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u306F openshain \u306E Company Workspace \u3067\u3059\u3002\u3053\u306E\u6307\u793A\u306F\u3001Claude Code \u3084 Codex \u306E\u3088\u3046\u306A\u5916\u90E8\u306E\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u304C MCP \u7D4C\u7531\u3067\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u6271\u3046\u3068\u304D\u306E\u3082\u306E\u3067\u3059\u3002openshain \u306E\u5BFE\u8A71\u578B CLI \u3082\u540C\u3058\u624B\u9806\u3067 Runtime \u3092\u4F7F\u3044\u307E\u3059\u3002\n\n\u4F1A\u793E\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u66F8\u304D\u3068\u96C6\u8A08\u306F openshain \u306E MCP tool \u3067\u884C\u3044\u307E\u3059\u3002Claude Code \u3084 Codex \u81EA\u8EAB\u306E Read\u3001Write\u3001Bash \u306F\u4F1A\u793E\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u306F\u4F7F\u3044\u307E\u305B\u3093\u3002Runtime \u3092\u901A\u3089\u306A\u304B\u3063\u305F\u64CD\u4F5C\u306F\u8A18\u9332\u306B\u6B8B\u3089\u306A\u3044\u305F\u3081\u3067\u3059\u3002\n\n- \u4F9D\u983C\u3092\u53D7\u3051\u305F\u3089\u3001\u307E\u305A `work_create` \u306B\u4F9D\u983C\u306E\u6587\u3092\u305D\u306E\u307E\u307E\u6E21\u3057\u3066 Work \u3092\u4F5C\u308B\n- \u30D5\u30A1\u30A4\u30EB\u306F `fs_list`\u3001`fs_search`\u3001`fs_read`\u3001`csv_read`\u3001`markdown_read` \u3067\u898B\u308B\u3002\u5408\u8A08\u3084\u4EF6\u6570\u306F `csv_aggregate` \u306B\u4EFB\u305B\u3001\u81EA\u5206\u3067\u5408\u8A08\u3057\u306A\u3044\n- \u66F8\u304F\u3068\u304D\u306F `fs_write` \u304B `csv_write`\n- \u7D42\u308F\u3063\u305F\u3089 `work_complete` \u306B\u3001\u4F55\u3092\u3057\u305F\u304B\u3068\u3001\u66F8\u3044\u305F\u30D5\u30A1\u30A4\u30EB\u3092\u6E21\u3059\u3002\u7D9A\u3051\u3089\u308C\u306A\u3044\u3068\u304D\u306F `work_fail`\n- `openshain.yaml` \u3068 `work/` \u306F Runtime \u306E\u3082\u306E\u3002\u5909\u66F4\u3057\u306A\u3044\n";
10
10
  export declare const CLAUDE_TEMPLATE = "@AGENTS.md\n";
11
11
  export interface InitOptions {
12
12
  workspaceRoot: string;
@@ -44,7 +44,7 @@ export const MCP_TEMPLATE = `${JSON.stringify({ mcpServers: { openshain: { comma
44
44
  /** What an outside agent reads before working in the folder. Codex reads AGENTS.md; Claude Code reads it through CLAUDE.md. */
45
45
  export const AGENTS_TEMPLATE = `# この会社フォルダで働くエージェントへ
46
46
 
47
- このフォルダは openshain の Company Workspace です。この指示は、Claude Code や Codex のような外部のエージェントが MCP 経由でこのフォルダを扱うときのものです。\`openshain run\` Runtime 自身が動くときは、Work の作成と完了を Runtime が行うので、下の work_* の手順は当てはまりません。
47
+ このフォルダは openshain の Company Workspace です。この指示は、Claude Code や Codex のような外部のエージェントが MCP 経由でこのフォルダを扱うときのものです。openshain の対話型 CLI も同じ手順で Runtime を使います。
48
48
 
49
49
  会社のファイルの読み書きと集計は openshain の MCP tool で行います。Claude Code や Codex 自身の Read、Write、Bash は会社のファイルには使いません。Runtime を通らなかった操作は記録に残らないためです。
50
50
 
@@ -96,7 +96,7 @@ export async function init({ workspaceRoot, write }) {
96
96
  write(`${path} はすでにあるので変更しません。`);
97
97
  }
98
98
  }
99
- write("company と principal を自分の会社に合わせ、api_key_env に書いた環境変数を設定してから openshain run を実行してください。");
99
+ write("company と principal を自分の会社に合わせ、api_key_env に書いた環境変数を設定してから openshain を実行してください。");
100
100
  }
101
101
  /**
102
102
  * Adds the openshain server to a .mcp.json that is already there, keeping every other server.
@@ -108,10 +108,10 @@ async function addToMcpConfig(path) {
108
108
  parsed = JSON.parse(await readFile(path, "utf8"));
109
109
  }
110
110
  catch {
111
- return `${path} はすでにあり、JSON として読めないので変更しません。openshain を手で登録してください。`;
111
+ return `${path} はすでにあり、JSON として読めないので変更しません。openshain を手動で登録してください。`;
112
112
  }
113
113
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
114
- return `${path} はすでにあり、形が違うので変更しません。openshain を手で登録してください。`;
114
+ return `${path} はすでにあり、形が違うので変更しません。openshain を手動で登録してください。`;
115
115
  }
116
116
  const config = parsed;
117
117
  const servers = config.mcpServers && typeof config.mcpServers === "object" && !Array.isArray(config.mcpServers)
@@ -1,5 +1,4 @@
1
- import { ASK_USER, RUNTIME_PROVIDER_ID } from "@openshain/agent";
2
- import { createToolRegistry, loadConfig } from "@openshain/core";
1
+ import { ASK_USER, createToolRegistry, loadConfig, RUNTIME_PROVIDER_ID, } from "@openshain/core";
3
2
  /** Every tool the model can call in this workspace, and the ones the allow lists hide. Needs no model provider. */
4
3
  export async function toolsList({ workspaceRoot, providers, write, }) {
5
4
  const config = await loadConfig(workspaceRoot);
@@ -1,5 +1,4 @@
1
- import { type AnyEvent, type RuntimeProviders, type Work } from "@openshain/core";
2
- import { type DriveOptions } from "./run.ts";
1
+ import { type AnyEvent, type Work } from "@openshain/core";
3
2
  export interface WorkListOptions {
4
3
  workspaceRoot: string;
5
4
  write: (line: string) => void;
@@ -14,10 +13,3 @@ export interface WorkShowOptions {
14
13
  /** Everything about one work: state, outcome, what the tools did, the usage, and who acts next. */
15
14
  export declare function workShow({ workspaceRoot, id, write }: WorkShowOptions): Promise<void>;
16
15
  export declare function describeWork(work: Work, events: AnyEvent[]): string[];
17
- export interface WorkResumeOptions extends DriveOptions {
18
- workspaceRoot: string;
19
- providers: RuntimeProviders;
20
- id: string;
21
- }
22
- /** Continues a work that stopped before its end, answering its questions when the caller can. */
23
- export declare function workResume(options: WorkResumeOptions): Promise<number>;
@@ -1,14 +1,13 @@
1
- import { pendingQuestions } from "@openshain/agent";
2
- import { createRuntime, isTerminal, parseWorkId, SESSION_WORK_TYPE, WorkStore, } from "@openshain/core";
1
+ import { parseWorkId, pendingQuestions, WorkStore, } from "@openshain/core";
3
2
  import { describeInput, padDisplay } from "../format.js";
4
3
  import { errorLabel, failureLabel, rejectionLabel, statusLabel } from "../labels.js";
4
+ import { nextActor } from "../report.js";
5
5
  import { formatUsage, summarizeUsage } from "../usage.js";
6
- import { drive, nextActor } from "./run.js";
7
6
  /** One line per work, oldest first. Works that cannot be read are reported, not hidden. */
8
7
  export async function workList({ workspaceRoot, write }) {
9
8
  const { works, problems } = await new WorkStore(workspaceRoot).list();
10
9
  if (works.length === 0 && problems.length === 0) {
11
- write('Work はまだありません。openshain run "<依頼>" で始められます。');
10
+ write("Work はまだありません。openshain で社員エージェントに依頼すると始まります。");
12
11
  return;
13
12
  }
14
13
  for (const work of works) {
@@ -62,24 +61,6 @@ export function describeWork(work, events) {
62
61
  lines.push(nextActor(work));
63
62
  return lines;
64
63
  }
65
- /** Continues a work that stopped before its end, answering its questions when the caller can. */
66
- export async function workResume(options) {
67
- const workId = parseWorkId(options.id);
68
- const work = await new WorkStore(options.workspaceRoot).get(workId);
69
- if (work.type === SESSION_WORK_TYPE) {
70
- options.write(`${work.id} は会話の記録のため、再開できません。会話は openshain で始め直してください。`);
71
- return 1;
72
- }
73
- if (isTerminal(work.status)) {
74
- options.write(`${work.id} は${statusLabel(work.status)}のため、再開できません。`);
75
- return 1;
76
- }
77
- const runtime = await createRuntime({
78
- workspaceRoot: options.workspaceRoot,
79
- providers: options.providers,
80
- });
81
- return drive(runtime, workId, options);
82
- }
83
64
  /** One line per tool call, in log order, with its outcome when it was rejected or failed. */
84
65
  function toolLines(events) {
85
66
  const lines = new Map();
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { AGENTS_TEMPLATE, CLAUDE_TEMPLATE, CONFIG_TEMPLATE, type InitOptions, init, MCP_TEMPLATE, } from "./commands/init.ts";
2
2
  export { type McpOptions, mcp } from "./commands/mcp.ts";
3
- export { type DriveOptions, drive, nextActor, type RunOptions, report, run, } from "./commands/run.ts";
4
3
  export { type ToolsListOptions, toolsList } from "./commands/tools.ts";
5
- export { describeWork, type WorkListOptions, type WorkResumeOptions, type WorkShowOptions, workList, workResume, workShow, } from "./commands/work.ts";
4
+ export { describeWork, type WorkListOptions, type WorkShowOptions, workList, workShow, } from "./commands/work.ts";
6
5
  export { ERROR_LABELS, errorLabel, FAILURE_LABELS, failureLabel, REJECTION_LABELS, rejectionLabel, STATUS_LABELS, statusLabel, } from "./labels.ts";
6
+ export { nextActor, progressLine, report } from "./report.ts";
7
7
  export { formatUsage, summarizeUsage, type UsageSummary } from "./usage.ts";
8
8
  export { findWorkspace } from "./workspace.ts";
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // openshain: Reference CLI agent for the openshain runtime
2
2
  export { AGENTS_TEMPLATE, CLAUDE_TEMPLATE, CONFIG_TEMPLATE, init, MCP_TEMPLATE, } from "./commands/init.js";
3
3
  export { mcp } from "./commands/mcp.js";
4
- export { drive, nextActor, report, run, } from "./commands/run.js";
5
4
  export { toolsList } from "./commands/tools.js";
6
- export { describeWork, workList, workResume, workShow, } from "./commands/work.js";
5
+ export { describeWork, workList, workShow, } from "./commands/work.js";
7
6
  export { ERROR_LABELS, errorLabel, FAILURE_LABELS, failureLabel, REJECTION_LABELS, rejectionLabel, STATUS_LABELS, statusLabel, } from "./labels.js";
7
+ export { nextActor, progressLine, report } from "./report.js";
8
8
  export { formatUsage, summarizeUsage } from "./usage.js";
9
9
  export { findWorkspace } from "./workspace.js";
package/dist/labels.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { FailureReason } from "@openshain/agent";
2
- import type { ErrorCode, ToolRejectionCode, WorkStatus } from "@openshain/core";
1
+ import type { ErrorCode, FailureReason, ToolRejectionCode, WorkStatus } from "@openshain/core";
3
2
  /** The words shown for a work's status. The log keeps the original value. */
4
3
  export declare const STATUS_LABELS: Record<WorkStatus, string>;
5
4
  /** Why a work failed, as a heading before the original detail. */
package/dist/labels.js CHANGED
@@ -23,6 +23,7 @@ export const REJECTION_LABELS = {
23
23
  reserved_path: "予約されたパス",
24
24
  outside_workspace: "workspace の外",
25
25
  invalid_path: "不正なパス",
26
+ limit_reached: "Tool 呼び出しの上限",
26
27
  };
27
28
  /** A heading for a runtime error, before the original message. */
28
29
  export const ERROR_LABELS = {
@@ -0,0 +1,6 @@
1
+ import { type AnyEvent, type Work } from "@openshain/core";
2
+ /** One line for a tool call, a rejection or a failure; nothing for the other events. `names` maps call ids to tool names. */
3
+ export declare function progressLine(event: AnyEvent, names: Map<string, string>): string | undefined;
4
+ /** The closing lines: what happened, what it cost, and who acts next. */
5
+ export declare function report(work: Work, events: AnyEvent[]): string[];
6
+ export declare function nextActor(work: Work): string;
@@ -1,39 +1,7 @@
1
- import { pendingQuestions, runWork } from "@openshain/agent";
2
- import { createRuntime, } from "@openshain/core";
3
- import { describeInput, truncate } from "../format.js";
4
- import { failureLabel, rejectionLabel, statusLabel } from "../labels.js";
5
- import { formatUsage, summarizeUsage } from "../usage.js";
6
- /** Creates a work for the request and drives it. Exit code 0 when the work completed. */
7
- export async function run(options) {
8
- const runtime = await createRuntime({
9
- workspaceRoot: options.workspaceRoot,
10
- providers: options.providers,
11
- });
12
- const work = await runtime.works.create({
13
- objective: options.objective,
14
- principal: runtime.config.principal.id,
15
- profession: runtime.config.profession.id,
16
- });
17
- options.write(`${work.id} を開始`);
18
- return drive(runtime, work.id, options);
19
- }
20
- /** Drives a work from its current state, printing one line per tool call, and closes with the report. */
21
- export async function drive(runtime, workId, options) {
22
- const names = new Map();
23
- const done = await runWork(runtime, workId, {
24
- ...(options.ask && { onInput: options.ask }),
25
- ...(options.signal && { signal: options.signal }),
26
- onEvent: (event) => {
27
- const line = progressLine(event, names);
28
- if (line)
29
- options.write(line);
30
- },
31
- });
32
- const events = await runtime.works.events(workId);
33
- for (const line of report(done, events))
34
- options.write(line);
35
- return done.status === "completed" ? 0 : 1;
36
- }
1
+ import { pendingQuestions, } from "@openshain/core";
2
+ import { describeInput, truncate } from "./format.js";
3
+ import { failureLabel, rejectionLabel, statusLabel } from "./labels.js";
4
+ import { formatUsage, summarizeUsage } from "./usage.js";
37
5
  /** One line for a tool call, a rejection or a failure; nothing for the other events. `names` maps call ids to tool names. */
38
6
  export function progressLine(event, names) {
39
7
  switch (event.type) {
@@ -94,7 +62,7 @@ export function nextActor(work) {
94
62
  case "cancelled":
95
63
  return "次に動く人はいません。";
96
64
  case "waiting_input":
97
- return `次は利用者の番です。openshain work resume ${work.id} で質問に答えると続きます。`;
65
+ return `次は利用者の番です。openshain の会話で /work resume ${work.id} を実行し、続きを依頼すると質問に答えられます。`;
98
66
  case "waiting_approval":
99
67
  return "次は利用者の番です。承認が要ります。";
100
68
  case "failed":
@@ -1,4 +1,4 @@
1
- import { type Runtime, type RuntimeProviders, type WorkId } from "@openshain/core";
1
+ import { type RuntimeProviders, type WorkId, WorkStore } from "@openshain/core";
2
2
  import { statusLabel } from "../labels.ts";
3
3
  /** logo and banner are the rows shown once when the screen opens: the wordmark, the version, the folder. */
4
4
  export type EntryKind = "user" | "assistant" | "progress" | "notice" | "question" | "line" | "logo" | "banner";
@@ -47,10 +47,13 @@ export interface Controller {
47
47
  export interface ControllerOptions {
48
48
  workspaceRoot: string;
49
49
  providers: RuntimeProviders;
50
- runtime?: Runtime;
51
50
  }
52
- /** The state behind the screen: a session, the works it starts, and the lines to show. */
51
+ /**
52
+ * The state behind the screen: a session, the works it starts, and the lines to show. The
53
+ * conversation reaches the runtime only as an MCP client of the workspace's own server, the way
54
+ * any other agent does; the records are read directly for the closing lines.
55
+ */
53
56
  export declare function createController(options: ControllerOptions): Promise<Controller>;
54
- /** Lines that close a work in the screen: the CLI's closing lines without the summary, which the clerk relays. */
55
- export declare function workReport(runtime: Runtime, workId: WorkId): Promise<string[]>;
57
+ /** Lines that close a work in the screen: the CLI's closing lines without the summary, which the agent relays. */
58
+ export declare function workReport(store: WorkStore, workId: WorkId): Promise<string[]>;
56
59
  export { statusLabel };
@@ -1,15 +1,16 @@
1
- import { createSession } from "@openshain/agent";
2
- import { createRuntime, } from "@openshain/core";
3
- import { progressLine, report } from "../commands/run.js";
1
+ import { connectInMemory, createSession } from "@openshain/agent";
2
+ import { loadConfig, OpenshainError, WorkStore, } from "@openshain/core";
3
+ import { createMcpServer } from "@openshain/mcp";
4
4
  import { toolsList } from "../commands/tools.js";
5
- import { workList, workResume, workShow } from "../commands/work.js";
5
+ import { workList, workShow } from "../commands/work.js";
6
6
  import { plain } from "../format.js";
7
7
  import { statusLabel } from "../labels.js";
8
+ import { progressLine, report } from "../report.js";
8
9
  import { LOGO_ROWS, VERSION } from "./banner.js";
9
10
  const HELP = [
10
11
  "/work list Work の一覧",
11
12
  "/work show <id> Work の詳細",
12
- "/work resume <id> 止まった Work を続ける",
13
+ "/work resume <id> 止まった Work を候補にする。次の依頼がそれに沿えば続ける",
13
14
  "/tools 使える Tool",
14
15
  "/quit 終わる",
15
16
  "↑ ↓ 前に送った行を入力欄に呼び戻す。いちばん下は新しい入力",
@@ -19,10 +20,30 @@ const HELP = [
19
20
  ];
20
21
  /** What the session's model hears when the person stops a work that waits for their answer. */
21
22
  const QUESTION_WITHDRAWN = "the person stopped the work while it waited for their answer; the question is still pending and the work can be resumed";
22
- /** The state behind the screen: a session, the works it starts, and the lines to show. */
23
+ /**
24
+ * The state behind the screen: a session, the works it starts, and the lines to show. The
25
+ * conversation reaches the runtime only as an MCP client of the workspace's own server, the way
26
+ * any other agent does; the records are read directly for the closing lines.
27
+ */
23
28
  export async function createController(options) {
24
- const runtime = options.runtime ??
25
- (await createRuntime({ workspaceRoot: options.workspaceRoot, providers: options.providers }));
29
+ const { workspaceRoot, providers } = options;
30
+ const config = await loadConfig(workspaceRoot, { modelProviders: Object.keys(providers.models) });
31
+ if (!config.model) {
32
+ throw new OpenshainError("config", "対話にはモデルが要ります。openshain.yaml に model を書いてください。Claude Code や Codex から使うだけなら要りません。");
33
+ }
34
+ const modelFactory = Object.hasOwn(providers.models, config.model.provider)
35
+ ? providers.models[config.model.provider]
36
+ : undefined;
37
+ if (!modelFactory) {
38
+ throw new OpenshainError("config", `unknown model provider "${config.model.provider}"`);
39
+ }
40
+ const model = modelFactory(config.model);
41
+ if (!model.describe().capabilities.tools) {
42
+ throw new OpenshainError("config", `model ${config.model.provider}/${config.model.model} cannot call tools; openshain needs a model with tool support`);
43
+ }
44
+ const server = await createMcpServer({ workspaceRoot, tools: providers.tools });
45
+ const client = await connectInMemory(server);
46
+ const store = new WorkStore(workspaceRoot);
26
47
  const listeners = new Set();
27
48
  let nextId = 1;
28
49
  const state = {
@@ -30,8 +51,8 @@ export async function createController(options) {
30
51
  busy: false,
31
52
  closed: false,
32
53
  status: {
33
- company: runtime.config.company.name,
34
- model: `${runtime.config.model.provider}/${runtime.config.model.model}`,
54
+ company: config.company.name,
55
+ model: `${config.model.provider}/${config.model.model}`,
35
56
  usage: { modelCalls: 0, inputTokens: 0, outputTokens: 0 },
36
57
  },
37
58
  };
@@ -63,8 +84,6 @@ export async function createController(options) {
63
84
  let aborter;
64
85
  let running;
65
86
  let closing;
66
- /** The child work of the current turn while it is unfinished. */
67
- let lastWorkId;
68
87
  const names = new Map();
69
88
  const ask = (workId, question) => {
70
89
  state.question = question;
@@ -90,60 +109,70 @@ export async function createController(options) {
90
109
  };
91
110
  /** The lines the CLI prints when a work ends, shown among the progress lines. */
92
111
  const closingLines = async (workId) => {
93
- for (const line of await workReport(runtime, workId))
112
+ for (const line of await workReport(store, workId))
94
113
  push("progress", line.trimStart());
95
114
  };
96
- const onWorkEvent = (workId, event) => {
97
- lastWorkId = workId;
98
- if (event.type === "work.status_changed") {
99
- state.status.work = {
100
- id: workId,
101
- status: event.payload.to,
102
- };
103
- }
104
- else if (event.type === "work.completed" || event.type === "work.failed") {
105
- lastWorkId = undefined;
106
- state.status.work = {
107
- id: workId,
108
- status: event.type === "work.completed" ? "completed" : "failed",
109
- };
110
- return closingLines(workId);
111
- }
112
- const line = progressLine(event, names);
113
- if (line)
114
- push("progress", line);
115
- else
116
- notify();
117
- };
118
- const session = await createSession(runtime, {
119
- onEvent: (event) => {
120
- if (event.type === "usage.recorded") {
121
- const { payload } = event;
122
- if (payload.kind === "model_inference") {
123
- state.status.usage.modelCalls += 1;
124
- state.status.usage.inputTokens += payload.usage.inputTokens;
125
- state.status.usage.outputTokens += payload.usage.outputTokens;
126
- notify();
115
+ let sessionId;
116
+ const session = await createSession(client, {
117
+ model,
118
+ config,
119
+ onEvent: (workId, event) => {
120
+ if (workId === sessionId) {
121
+ if (event.type === "usage.recorded") {
122
+ const { payload } = event;
123
+ if (payload.kind === "model_inference") {
124
+ state.status.usage.modelCalls += 1;
125
+ state.status.usage.inputTokens += payload.usage.inputTokens;
126
+ state.status.usage.outputTokens += payload.usage.outputTokens;
127
+ notify();
128
+ }
127
129
  }
130
+ return;
131
+ }
132
+ if (event.type === "work.status_changed") {
133
+ state.status.work = {
134
+ id: workId,
135
+ status: event.payload.to,
136
+ };
137
+ notify();
138
+ return;
139
+ }
140
+ if (event.type === "work.completed" || event.type === "work.failed") {
141
+ state.status.work = {
142
+ id: workId,
143
+ status: event.type === "work.completed" ? "completed" : "failed",
144
+ };
145
+ return closingLines(workId);
146
+ }
147
+ // The work_* calls are the loop's own bookkeeping; the closing lines already say the work ended.
148
+ if (event.type === "tool.called" &&
149
+ event.payload.name.startsWith("work_")) {
150
+ names.set(event.payload.callId, event.payload.name);
151
+ return;
128
152
  }
153
+ const line = progressLine(event, names);
154
+ if (line)
155
+ push("progress", line);
156
+ else
157
+ notify();
129
158
  },
130
- onWorkEvent,
131
159
  onInput: ask,
132
160
  });
161
+ sessionId = session.id;
133
162
  state.status.agentName = session.agentName;
134
163
  for (const row of LOGO_ROWS)
135
164
  push("logo", row);
136
165
  push("banner", `openshain ${VERSION}`);
137
- push("banner", runtime.workspaceRoot);
166
+ push("banner", workspaceRoot);
138
167
  const stopped = (workId) => workId
139
- ? `止めました。${workId} は途中のまま残っています。/work resume ${workId} で続けられます。`
168
+ ? `止めました。${workId} は途中のまま残っています。/work resume ${workId} で続けられるようにします。`
140
169
  : "止めました。";
141
170
  const explain = (result) => {
142
171
  switch (result.stopped) {
143
172
  case "turn_limit":
144
- return "社員エージェントが 1 回の返答でできる回数を超えたので、ここで止めました。続きを依頼できます。";
173
+ return "社員エージェントが 1 回の返答でできる回数を超えたので、ここで止めました。続きは改めて依頼してください。";
145
174
  case "aborted":
146
- return stopped(lastWorkId);
175
+ return stopped(result.work);
147
176
  case "max_tokens":
148
177
  return "返答が長さの上限で切れました。";
149
178
  case "refusal":
@@ -198,24 +227,13 @@ export async function createController(options) {
198
227
  else if (name === "work" && sub === "show" && id)
199
228
  await capture((write) => workShow({ workspaceRoot: options.workspaceRoot, id, write }));
200
229
  else if (name === "work" && sub === "resume" && id) {
201
- await stoppable(async (signal) => {
202
- try {
203
- await workResume({
204
- workspaceRoot: options.workspaceRoot,
205
- providers: options.providers,
206
- id,
207
- signal,
208
- write: (text) => push("line", text),
209
- ask: (q) => ask(id, q),
210
- });
211
- }
212
- catch (err) {
213
- if (!signal.aborted)
214
- push("notice", message(err));
215
- }
216
- if (signal.aborted)
217
- push("notice", stopped(id));
218
- });
230
+ try {
231
+ const work = await session.select(id);
232
+ push("notice", `${work.id}(${statusLabel(work.status)}、${work.objective})を候補にしました。次の依頼がこの Work に沿えば続けます。`);
233
+ }
234
+ catch (err) {
235
+ push("notice", message(err));
236
+ }
219
237
  }
220
238
  else if (name === "resume") {
221
239
  push("notice", "セッションの再開はまだありません。止まった Work を続けるなら /work resume <id> です。");
@@ -229,9 +247,17 @@ export async function createController(options) {
229
247
  aborter?.abort();
230
248
  settleQuestion();
231
249
  await running;
232
- await session.close();
233
- state.closed = true;
234
- notify();
250
+ try {
251
+ await session.close();
252
+ }
253
+ catch (err) {
254
+ push("notice", `会話の記録を閉じられませんでした。${message(err)}`);
255
+ }
256
+ finally {
257
+ await client.close().catch(() => undefined);
258
+ state.closed = true;
259
+ notify();
260
+ }
235
261
  })();
236
262
  return closing;
237
263
  }
@@ -289,10 +315,10 @@ export async function createController(options) {
289
315
  close,
290
316
  };
291
317
  }
292
- /** Lines that close a work in the screen: the CLI's closing lines without the summary, which the clerk relays. */
293
- export async function workReport(runtime, workId) {
294
- const work = await runtime.works.get(workId);
295
- const events = await runtime.works.events(workId);
318
+ /** Lines that close a work in the screen: the CLI's closing lines without the summary, which the agent relays. */
319
+ export async function workReport(store, workId) {
320
+ const work = await store.get(workId);
321
+ const events = await store.events(workId);
296
322
  const lines = report(work, events);
297
323
  return work.status === "completed" ? ["完了。", ...lines.slice(1)] : lines;
298
324
  }
package/dist/tui/index.js CHANGED
@@ -40,6 +40,6 @@ export async function startTui(options) {
40
40
  finally {
41
41
  leave();
42
42
  }
43
- console.log(`会話を終えました。記録は openshain work show ${controller.sessionId} で読めます。`);
43
+ console.log(`会話を終えました。記録は openshain work show ${controller.sessionId} で参照します。`);
44
44
  return 0;
45
45
  }
package/dist/usage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AnyEvent } from "@openshain/core";
1
+ import { type AnyEvent } from "@openshain/core";
2
2
  export interface UsageSummary {
3
3
  modelCalls: number;
4
4
  toolCalls: number;
package/dist/usage.js CHANGED
@@ -1,4 +1,4 @@
1
- import { countToolCalls } from "@openshain/agent";
1
+ import { countToolCalls } from "@openshain/core";
2
2
  /** Totals over a work's events: calls and tokens. */
3
3
  export function summarizeUsage(events) {
4
4
  const summary = {
package/dist/workspace.js CHANGED
@@ -12,7 +12,7 @@ export async function findWorkspace(start) {
12
12
  catch {
13
13
  const parent = dirname(dir);
14
14
  if (parent === dir) {
15
- throw new OpenshainError("config", `${CONFIG_FILE_NAME} が見つかりません。${resolve(start)} から上に向かって探しました。openshain init で作れます。`);
15
+ throw new OpenshainError("config", `${CONFIG_FILE_NAME} が見つかりません。${resolve(start)} から上に向かって探しました。openshain init で作成します。`);
16
16
  }
17
17
  dir = parent;
18
18
  }