openshain 0.7.0 → 0.9.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.
- package/dist/bin.js +26 -0
- package/dist/commands/observe.d.ts +18 -0
- package/dist/commands/observe.js +25 -0
- package/dist/commands/work.js +30 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/labels.js +3 -0
- package/dist/report.js +2 -2
- package/dist/tui/controller.js +4 -1
- package/dist/usage.d.ts +1 -11
- package/dist/usage.js +0 -25
- package/package.json +5 -5
- package/src/bin.ts +35 -1
- package/src/commands/observe.ts +38 -0
- package/src/commands/work.ts +34 -1
- package/src/index.ts +4 -1
- package/src/labels.ts +3 -0
- package/src/report.ts +2 -1
- package/src/tui/controller.ts +4 -1
- package/src/usage.ts +1 -34
package/dist/bin.js
CHANGED
|
@@ -7,6 +7,7 @@ import { init } from "./commands/init.js";
|
|
|
7
7
|
import { knowledgeBuild, knowledgeCheck } from "./commands/knowledge.js";
|
|
8
8
|
import { knowledgeAdd } from "./commands/knowledge-add.js";
|
|
9
9
|
import { mcp } from "./commands/mcp.js";
|
|
10
|
+
import { observeCommand } from "./commands/observe.js";
|
|
10
11
|
import { principalCheck } from "./commands/principal.js";
|
|
11
12
|
import { toolsList } from "./commands/tools.js";
|
|
12
13
|
import { workList, workShow } from "./commands/work.js";
|
|
@@ -24,8 +25,13 @@ const USAGE = `使い方:
|
|
|
24
25
|
openshain knowledge check 同じ検証を、索引を書かずに行う(--stale で古い資料も報告)
|
|
25
26
|
openshain knowledge add 決まりを 1 件、質問に答えて追加する
|
|
26
27
|
openshain principal check <id> その人の社員エージェントがどこで働き、何ができるかを確かめる
|
|
28
|
+
openshain observe <type> 起きたことを記録する。当たる義務があれば Work を作る
|
|
27
29
|
openshain mcp MCP Server を stdio で起動する
|
|
28
30
|
|
|
31
|
+
--source <name> observe で、それをどこから知ったか(既定は cli)
|
|
32
|
+
--at <時刻> observe で、それが起きた時刻(既定はいま)
|
|
33
|
+
--ref <path> observe で、中身の置き場
|
|
34
|
+
|
|
29
35
|
--principal <id> この端末が誰として働くか。principals/ に書いた id
|
|
30
36
|
環境変数 OPENSHAIN_PRINCIPAL でも指定できる。本人確認はしない
|
|
31
37
|
--workspace <dir> 起点のディレクトリ。省略時はカレントディレクトリ
|
|
@@ -58,6 +64,9 @@ async function main(argv) {
|
|
|
58
64
|
principal: { type: "string" },
|
|
59
65
|
help: { type: "boolean", short: "h" },
|
|
60
66
|
stale: { type: "boolean" },
|
|
67
|
+
source: { type: "string" },
|
|
68
|
+
at: { type: "string" },
|
|
69
|
+
ref: { type: "string" },
|
|
61
70
|
},
|
|
62
71
|
allowPositionals: true,
|
|
63
72
|
}));
|
|
@@ -118,6 +127,23 @@ async function main(argv) {
|
|
|
118
127
|
const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
|
|
119
128
|
return await principalCheck({ workspaceRoot, id, write });
|
|
120
129
|
}
|
|
130
|
+
case "observe": {
|
|
131
|
+
const type = rest[0];
|
|
132
|
+
if (!type) {
|
|
133
|
+
write(USAGE);
|
|
134
|
+
return 2;
|
|
135
|
+
}
|
|
136
|
+
const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
|
|
137
|
+
return await observeCommand({
|
|
138
|
+
workspaceRoot,
|
|
139
|
+
type,
|
|
140
|
+
source: values.source ?? "cli",
|
|
141
|
+
...(values.at !== undefined && { at: values.at }),
|
|
142
|
+
...(values.ref !== undefined && { ref: values.ref }),
|
|
143
|
+
as: actingFor(values.principal),
|
|
144
|
+
write,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
121
147
|
case "work": {
|
|
122
148
|
const sub = rest[0];
|
|
123
149
|
const id = rest[1] ?? "";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ObserveOptions {
|
|
2
|
+
workspaceRoot: string;
|
|
3
|
+
/** What happened, in the company's own word. An obligation names the same one. */
|
|
4
|
+
type: string;
|
|
5
|
+
source: string;
|
|
6
|
+
/** When it happened, if that is earlier than now. */
|
|
7
|
+
at?: string | undefined;
|
|
8
|
+
/** Where the thing itself is, inside the company folder. */
|
|
9
|
+
ref?: string | undefined;
|
|
10
|
+
as?: string | undefined;
|
|
11
|
+
write: (line: string) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Writes down something that happened, and starts whatever the company said should follow. The
|
|
15
|
+
* source of the fact is whatever ran this: a script watching a mailbox, a folder, or a person
|
|
16
|
+
* typing it. openshain records that it happened, and the obligations decide what that means.
|
|
17
|
+
*/
|
|
18
|
+
export declare function observeCommand(options: ObserveOptions): Promise<number>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { loadConfig, observe } from "@openshain/core";
|
|
2
|
+
/**
|
|
3
|
+
* Writes down something that happened, and starts whatever the company said should follow. The
|
|
4
|
+
* source of the fact is whatever ran this: a script watching a mailbox, a folder, or a person
|
|
5
|
+
* typing it. openshain records that it happened, and the obligations decide what that means.
|
|
6
|
+
*/
|
|
7
|
+
export async function observeCommand(options) {
|
|
8
|
+
const { workspaceRoot, type, source, at, ref, as, write } = options;
|
|
9
|
+
const config = await loadConfig(workspaceRoot, { ...(as !== undefined && { as }) });
|
|
10
|
+
const { observation, works } = await observe(workspaceRoot, config.principal.id, {
|
|
11
|
+
type,
|
|
12
|
+
source,
|
|
13
|
+
scope: { profession: config.profession.id },
|
|
14
|
+
...(at !== undefined && { observedAt: at }),
|
|
15
|
+
...(ref !== undefined && { payloadRef: ref }),
|
|
16
|
+
});
|
|
17
|
+
write(`${observation.id} ${observation.type}(${observation.source})`);
|
|
18
|
+
if (works.length === 0) {
|
|
19
|
+
write(" この観測に当たる義務はありません。記録だけ残しました");
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
for (const id of works)
|
|
23
|
+
write(` Work を作りました ${id}`);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
package/dist/commands/work.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { loadConfig, mayReadWork, noSuchWork, PRINCIPALS_DIR_NAME, parseWorkId, pendingApprovals, pendingQuestions, readPrincipals, WorkStore, } from "@openshain/core";
|
|
2
|
+
import { loadConfig, mayReadWork, measureWork, noSuchWork, PRINCIPALS_DIR_NAME, parseWorkId, pendingApprovals, pendingQuestions, readPrincipals, summarizeUsage, WorkStore, } from "@openshain/core";
|
|
3
3
|
import { describeInput, padDisplay } from "../format.js";
|
|
4
4
|
import { errorLabel, failureLabel, rejectionLabel, statusLabel } from "../labels.js";
|
|
5
5
|
import { nextActor } from "../report.js";
|
|
6
|
-
import { formatUsage
|
|
6
|
+
import { formatUsage } from "../usage.js";
|
|
7
7
|
/**
|
|
8
8
|
* The person this terminal works for, out of the people the company has written. Nobody written:
|
|
9
9
|
* nobody to be, and every work is read, as before there was more than one person.
|
|
@@ -52,6 +52,10 @@ export function describeWork(work, events) {
|
|
|
52
52
|
];
|
|
53
53
|
if (work.agentName)
|
|
54
54
|
lines.push(`名前 ${work.agentName}(社員エージェント)`);
|
|
55
|
+
// A work nobody asked for says where it came from, so it is not a mystery in the list.
|
|
56
|
+
if (work.observation) {
|
|
57
|
+
lines.push(`きっかけ ${work.observation}${work.obligation ? `(${work.obligation})` : ""}`);
|
|
58
|
+
}
|
|
55
59
|
if (work.startedAt)
|
|
56
60
|
lines.push(`開始 ${work.startedAt}`);
|
|
57
61
|
if (work.completedAt)
|
|
@@ -81,9 +85,33 @@ export function describeWork(work, events) {
|
|
|
81
85
|
lines.push(line);
|
|
82
86
|
}
|
|
83
87
|
lines.push(formatUsage(summarizeUsage(events)));
|
|
88
|
+
lines.push(measureLine(measureWork(work, events)));
|
|
84
89
|
lines.push(nextActor(work));
|
|
85
90
|
return lines;
|
|
86
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* What the work asked of a person and what the rules stopped, in one line. Only what happened is
|
|
94
|
+
* written: a work nobody was asked about says so, rather than listing three zeros.
|
|
95
|
+
*/
|
|
96
|
+
function measureLine(measures) {
|
|
97
|
+
const { intervention, quality, attention, coverage } = measures;
|
|
98
|
+
const asked = [
|
|
99
|
+
intervention.clarification > 0 ? `質問 ${intervention.clarification}` : "",
|
|
100
|
+
intervention.approval > 0 ? `承認 ${intervention.approval}` : "",
|
|
101
|
+
intervention.expertReview > 0 ? `資格者の判断 ${intervention.expertReview}` : "",
|
|
102
|
+
].filter(Boolean);
|
|
103
|
+
const notes = [
|
|
104
|
+
quality.corrected > 0 ? `資格者が書き換え ${quality.corrected}` : "",
|
|
105
|
+
quality.stoppedByRule > 0 ? `規則が止めた ${quality.stoppedByRule}` : "",
|
|
106
|
+
coverage.seconds !== undefined ? `所要 ${coverage.seconds} 秒` : "",
|
|
107
|
+
].filter(Boolean);
|
|
108
|
+
const called = attention.calls === 0
|
|
109
|
+
? coverage.finished
|
|
110
|
+
? "人を呼ばずに終わりました"
|
|
111
|
+
: "まだ人を呼んでいません"
|
|
112
|
+
: `人を呼んだ回数 ${attention.calls}(${asked.join("、")})`;
|
|
113
|
+
return [called, ...notes].join(" ");
|
|
114
|
+
}
|
|
87
115
|
/**
|
|
88
116
|
* How a reviewer is named on the screen. The role of a reviewer is a qualification the company
|
|
89
117
|
* names (tax-accountant), not one of the roles a person handles, so the word comes first and the
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
export { summarizeUsage, type UsageSummary } from "@openshain/core";
|
|
1
2
|
export { AGENTS_TEMPLATE, CLAUDE_TEMPLATE, CONFIG_TEMPLATE, type InitOptions, init, MCP_TEMPLATE, } from "./commands/init.ts";
|
|
2
3
|
export { type McpOptions, mcp } from "./commands/mcp.ts";
|
|
3
4
|
export { type ToolsListOptions, toolsList } from "./commands/tools.ts";
|
|
4
5
|
export { describeWork, type WorkListOptions, type WorkShowOptions, workList, workShow, } from "./commands/work.ts";
|
|
5
6
|
export { ERROR_LABELS, errorLabel, FAILURE_LABELS, failureLabel, REJECTION_LABELS, rejectionLabel, STATUS_LABELS, statusLabel, } from "./labels.ts";
|
|
6
7
|
export { nextActor, progressLine, report } from "./report.ts";
|
|
7
|
-
export { formatUsage
|
|
8
|
+
export { formatUsage } from "./usage.ts";
|
|
8
9
|
export { findWorkspace } from "./workspace.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// openshain: Reference CLI agent for the openshain runtime
|
|
2
|
+
// The sums live in core, where anything that reads a record can reach them.
|
|
3
|
+
export { summarizeUsage } from "@openshain/core";
|
|
2
4
|
export { AGENTS_TEMPLATE, CLAUDE_TEMPLATE, CONFIG_TEMPLATE, init, MCP_TEMPLATE, } from "./commands/init.js";
|
|
3
5
|
export { mcp } from "./commands/mcp.js";
|
|
4
6
|
export { toolsList } from "./commands/tools.js";
|
|
5
7
|
export { describeWork, workList, workShow, } from "./commands/work.js";
|
|
6
8
|
export { ERROR_LABELS, errorLabel, FAILURE_LABELS, failureLabel, REJECTION_LABELS, rejectionLabel, STATUS_LABELS, statusLabel, } from "./labels.js";
|
|
7
9
|
export { nextActor, progressLine, report } from "./report.js";
|
|
8
|
-
export { formatUsage
|
|
10
|
+
export { formatUsage } from "./usage.js";
|
|
9
11
|
export { findWorkspace } from "./workspace.js";
|
package/dist/labels.js
CHANGED
|
@@ -5,6 +5,7 @@ export const STATUS_LABELS = {
|
|
|
5
5
|
waiting_input: "利用者の入力待ち",
|
|
6
6
|
waiting_approval: "承認待ち",
|
|
7
7
|
waiting_external: "外部の応答待ち",
|
|
8
|
+
paused: "停止中",
|
|
8
9
|
completed: "完了",
|
|
9
10
|
failed: "失敗",
|
|
10
11
|
cancelled: "取り消し",
|
|
@@ -27,6 +28,8 @@ export const REJECTION_LABELS = {
|
|
|
27
28
|
denied: "権限の表で不許可",
|
|
28
29
|
rejected_by_person: "承認されなかった",
|
|
29
30
|
path_changed: "承認したときと書き込み先が変わった",
|
|
31
|
+
base_changed: "承認したときからファイルが変わった",
|
|
32
|
+
not_read: "読まずに置き換えようとした",
|
|
30
33
|
out_of_range: "範囲の外",
|
|
31
34
|
};
|
|
32
35
|
/** A heading for a runtime error, before the original message. */
|
package/dist/report.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { pendingQuestions, } from "@openshain/core";
|
|
1
|
+
import { pendingQuestions, summarizeUsage, } from "@openshain/core";
|
|
2
2
|
import { describeInput, truncate } from "./format.js";
|
|
3
3
|
import { failureLabel, rejectionLabel, statusLabel } from "./labels.js";
|
|
4
|
-
import { formatUsage
|
|
4
|
+
import { formatUsage } from "./usage.js";
|
|
5
5
|
/** One line for a tool call, a rejection or a failure; nothing for the other events. `names` maps call ids to tool names. */
|
|
6
6
|
export function progressLine(event, names) {
|
|
7
7
|
switch (event.type) {
|
package/dist/tui/controller.js
CHANGED
|
@@ -312,7 +312,10 @@ export async function createController(options) {
|
|
|
312
312
|
case "aborted":
|
|
313
313
|
return stopped(result.work);
|
|
314
314
|
case "max_tokens":
|
|
315
|
-
|
|
315
|
+
// The answer ran into the limit mid-sentence, so a work it opened holds nothing yet.
|
|
316
|
+
return result.work
|
|
317
|
+
? `返答が長さの上限で切れました。${result.work} は途中のまま残っています。/work resume ${result.work} で続けられるようにします。一度に扱う量を分けて依頼すると、上限に当たらずに終わります。`
|
|
318
|
+
: "返答が長さの上限で切れました。";
|
|
316
319
|
case "refusal":
|
|
317
320
|
return "社員エージェントが続けられないと言っています。";
|
|
318
321
|
case "model_error":
|
package/dist/usage.d.ts
CHANGED
|
@@ -1,12 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export interface UsageSummary {
|
|
3
|
-
modelCalls: number;
|
|
4
|
-
toolCalls: number;
|
|
5
|
-
inputTokens: number;
|
|
6
|
-
/** The part of inputTokens a prompt cache served. */
|
|
7
|
-
cachedInputTokens: number;
|
|
8
|
-
outputTokens: number;
|
|
9
|
-
}
|
|
10
|
-
/** Totals over a work's events: calls and tokens. */
|
|
11
|
-
export declare function summarizeUsage(events: AnyEvent[]): UsageSummary;
|
|
1
|
+
import type { UsageSummary } from "@openshain/core";
|
|
12
2
|
export declare function formatUsage(summary: UsageSummary): string;
|
package/dist/usage.js
CHANGED
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
import { countToolCalls } from "@openshain/core";
|
|
2
|
-
/** Totals over a work's events: calls and tokens. */
|
|
3
|
-
export function summarizeUsage(events) {
|
|
4
|
-
const summary = {
|
|
5
|
-
modelCalls: 0,
|
|
6
|
-
toolCalls: 0,
|
|
7
|
-
inputTokens: 0,
|
|
8
|
-
cachedInputTokens: 0,
|
|
9
|
-
outputTokens: 0,
|
|
10
|
-
};
|
|
11
|
-
for (const event of events) {
|
|
12
|
-
if (event.type === "model.requested")
|
|
13
|
-
summary.modelCalls += 1;
|
|
14
|
-
if (event.type === "usage.recorded") {
|
|
15
|
-
const { payload } = event;
|
|
16
|
-
if (payload.kind === "model_inference") {
|
|
17
|
-
summary.inputTokens += payload.usage.inputTokens;
|
|
18
|
-
summary.cachedInputTokens += payload.usage.cachedInputTokens ?? 0;
|
|
19
|
-
summary.outputTokens += payload.usage.outputTokens;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
summary.toolCalls = countToolCalls(events);
|
|
24
|
-
return summary;
|
|
25
|
-
}
|
|
26
1
|
export function formatUsage(summary) {
|
|
27
2
|
if (summary.modelCalls === 0 && summary.inputTokens === 0 && summary.outputTokens === 0) {
|
|
28
3
|
return `model 呼び出し 0 回、Tool 呼び出し ${summary.toolCalls} 回。model の使用量の記録はない`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openshain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Reference CLI of the openshain agent harness",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openshain",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
52
|
-
"@openshain/agent": "0.
|
|
53
|
-
"@openshain/core": "0.
|
|
54
|
-
"@openshain/mcp": "0.
|
|
55
|
-
"@openshain/tools": "0.
|
|
52
|
+
"@openshain/agent": "0.9.0",
|
|
53
|
+
"@openshain/core": "0.9.0",
|
|
54
|
+
"@openshain/mcp": "0.9.0",
|
|
55
|
+
"@openshain/tools": "0.9.0",
|
|
56
56
|
"ink": "7.1.1",
|
|
57
57
|
"marked": "18.0.12",
|
|
58
58
|
"react": "19.2.8"
|
package/src/bin.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { init } from "./commands/init.ts";
|
|
|
7
7
|
import { knowledgeBuild, knowledgeCheck } from "./commands/knowledge.ts";
|
|
8
8
|
import { knowledgeAdd } from "./commands/knowledge-add.ts";
|
|
9
9
|
import { mcp } from "./commands/mcp.ts";
|
|
10
|
+
import { observeCommand } from "./commands/observe.ts";
|
|
10
11
|
import { principalCheck } from "./commands/principal.ts";
|
|
11
12
|
import { toolsList } from "./commands/tools.ts";
|
|
12
13
|
import { workList, workShow } from "./commands/work.ts";
|
|
@@ -25,8 +26,13 @@ const USAGE = `使い方:
|
|
|
25
26
|
openshain knowledge check 同じ検証を、索引を書かずに行う(--stale で古い資料も報告)
|
|
26
27
|
openshain knowledge add 決まりを 1 件、質問に答えて追加する
|
|
27
28
|
openshain principal check <id> その人の社員エージェントがどこで働き、何ができるかを確かめる
|
|
29
|
+
openshain observe <type> 起きたことを記録する。当たる義務があれば Work を作る
|
|
28
30
|
openshain mcp MCP Server を stdio で起動する
|
|
29
31
|
|
|
32
|
+
--source <name> observe で、それをどこから知ったか(既定は cli)
|
|
33
|
+
--at <時刻> observe で、それが起きた時刻(既定はいま)
|
|
34
|
+
--ref <path> observe で、中身の置き場
|
|
35
|
+
|
|
30
36
|
--principal <id> この端末が誰として働くか。principals/ に書いた id
|
|
31
37
|
環境変数 OPENSHAIN_PRINCIPAL でも指定できる。本人確認はしない
|
|
32
38
|
--workspace <dir> 起点のディレクトリ。省略時はカレントディレクトリ
|
|
@@ -52,7 +58,15 @@ function actingFor(flag: string | undefined): string | undefined {
|
|
|
52
58
|
|
|
53
59
|
async function main(argv: string[]): Promise<number> {
|
|
54
60
|
const write = (line: string) => console.log(plain(line));
|
|
55
|
-
let values: {
|
|
61
|
+
let values: {
|
|
62
|
+
workspace?: string;
|
|
63
|
+
principal?: string;
|
|
64
|
+
help?: boolean;
|
|
65
|
+
stale?: boolean;
|
|
66
|
+
source?: string;
|
|
67
|
+
at?: string;
|
|
68
|
+
ref?: string;
|
|
69
|
+
};
|
|
56
70
|
let positionals: string[];
|
|
57
71
|
try {
|
|
58
72
|
({ values, positionals } = parseArgs({
|
|
@@ -62,6 +76,9 @@ async function main(argv: string[]): Promise<number> {
|
|
|
62
76
|
principal: { type: "string" },
|
|
63
77
|
help: { type: "boolean", short: "h" },
|
|
64
78
|
stale: { type: "boolean" },
|
|
79
|
+
source: { type: "string" },
|
|
80
|
+
at: { type: "string" },
|
|
81
|
+
ref: { type: "string" },
|
|
65
82
|
},
|
|
66
83
|
allowPositionals: true,
|
|
67
84
|
}));
|
|
@@ -120,6 +137,23 @@ async function main(argv: string[]): Promise<number> {
|
|
|
120
137
|
const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
|
|
121
138
|
return await principalCheck({ workspaceRoot, id, write });
|
|
122
139
|
}
|
|
140
|
+
case "observe": {
|
|
141
|
+
const type = rest[0];
|
|
142
|
+
if (!type) {
|
|
143
|
+
write(USAGE);
|
|
144
|
+
return 2;
|
|
145
|
+
}
|
|
146
|
+
const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
|
|
147
|
+
return await observeCommand({
|
|
148
|
+
workspaceRoot,
|
|
149
|
+
type,
|
|
150
|
+
source: values.source ?? "cli",
|
|
151
|
+
...(values.at !== undefined && { at: values.at }),
|
|
152
|
+
...(values.ref !== undefined && { ref: values.ref }),
|
|
153
|
+
as: actingFor(values.principal),
|
|
154
|
+
write,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
123
157
|
case "work": {
|
|
124
158
|
const sub = rest[0];
|
|
125
159
|
const id = rest[1] ?? "";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { loadConfig, observe } from "@openshain/core";
|
|
2
|
+
|
|
3
|
+
export interface ObserveOptions {
|
|
4
|
+
workspaceRoot: string;
|
|
5
|
+
/** What happened, in the company's own word. An obligation names the same one. */
|
|
6
|
+
type: string;
|
|
7
|
+
source: string;
|
|
8
|
+
/** When it happened, if that is earlier than now. */
|
|
9
|
+
at?: string | undefined;
|
|
10
|
+
/** Where the thing itself is, inside the company folder. */
|
|
11
|
+
ref?: string | undefined;
|
|
12
|
+
as?: string | undefined;
|
|
13
|
+
write: (line: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Writes down something that happened, and starts whatever the company said should follow. The
|
|
18
|
+
* source of the fact is whatever ran this: a script watching a mailbox, a folder, or a person
|
|
19
|
+
* typing it. openshain records that it happened, and the obligations decide what that means.
|
|
20
|
+
*/
|
|
21
|
+
export async function observeCommand(options: ObserveOptions): Promise<number> {
|
|
22
|
+
const { workspaceRoot, type, source, at, ref, as, write } = options;
|
|
23
|
+
const config = await loadConfig(workspaceRoot, { ...(as !== undefined && { as }) });
|
|
24
|
+
const { observation, works } = await observe(workspaceRoot, config.principal.id, {
|
|
25
|
+
type,
|
|
26
|
+
source,
|
|
27
|
+
scope: { profession: config.profession.id },
|
|
28
|
+
...(at !== undefined && { observedAt: at }),
|
|
29
|
+
...(ref !== undefined && { payloadRef: ref }),
|
|
30
|
+
});
|
|
31
|
+
write(`${observation.id} ${observation.type}(${observation.source})`);
|
|
32
|
+
if (works.length === 0) {
|
|
33
|
+
write(" この観測に当たる義務はありません。記録だけ残しました");
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
for (const id of works) write(` Work を作りました ${id}`);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
package/src/commands/work.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type Event,
|
|
5
5
|
loadConfig,
|
|
6
6
|
mayReadWork,
|
|
7
|
+
measureWork,
|
|
7
8
|
noSuchWork,
|
|
8
9
|
PRINCIPALS_DIR_NAME,
|
|
9
10
|
type Principal,
|
|
@@ -11,13 +12,15 @@ import {
|
|
|
11
12
|
pendingApprovals,
|
|
12
13
|
pendingQuestions,
|
|
13
14
|
readPrincipals,
|
|
15
|
+
summarizeUsage,
|
|
14
16
|
type Work,
|
|
17
|
+
type WorkMeasures,
|
|
15
18
|
WorkStore,
|
|
16
19
|
} from "@openshain/core";
|
|
17
20
|
import { describeInput, padDisplay } from "../format.ts";
|
|
18
21
|
import { errorLabel, failureLabel, rejectionLabel, statusLabel } from "../labels.ts";
|
|
19
22
|
import { nextActor } from "../report.ts";
|
|
20
|
-
import { formatUsage
|
|
23
|
+
import { formatUsage } from "../usage.ts";
|
|
21
24
|
|
|
22
25
|
export interface WorkListOptions {
|
|
23
26
|
workspaceRoot: string;
|
|
@@ -85,6 +88,10 @@ export function describeWork(work: Work, events: AnyEvent[]): string[] {
|
|
|
85
88
|
`作成 ${work.createdAt}`,
|
|
86
89
|
];
|
|
87
90
|
if (work.agentName) lines.push(`名前 ${work.agentName}(社員エージェント)`);
|
|
91
|
+
// A work nobody asked for says where it came from, so it is not a mystery in the list.
|
|
92
|
+
if (work.observation) {
|
|
93
|
+
lines.push(`きっかけ ${work.observation}${work.obligation ? `(${work.obligation})` : ""}`);
|
|
94
|
+
}
|
|
88
95
|
if (work.startedAt) lines.push(`開始 ${work.startedAt}`);
|
|
89
96
|
if (work.completedAt) lines.push(`終了 ${work.completedAt}`);
|
|
90
97
|
if (work.outcome) {
|
|
@@ -113,10 +120,36 @@ export function describeWork(work: Work, events: AnyEvent[]): string[] {
|
|
|
113
120
|
for (const line of calls) lines.push(line);
|
|
114
121
|
}
|
|
115
122
|
lines.push(formatUsage(summarizeUsage(events)));
|
|
123
|
+
lines.push(measureLine(measureWork(work, events)));
|
|
116
124
|
lines.push(nextActor(work));
|
|
117
125
|
return lines;
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
/**
|
|
129
|
+
* What the work asked of a person and what the rules stopped, in one line. Only what happened is
|
|
130
|
+
* written: a work nobody was asked about says so, rather than listing three zeros.
|
|
131
|
+
*/
|
|
132
|
+
function measureLine(measures: WorkMeasures): string {
|
|
133
|
+
const { intervention, quality, attention, coverage } = measures;
|
|
134
|
+
const asked = [
|
|
135
|
+
intervention.clarification > 0 ? `質問 ${intervention.clarification}` : "",
|
|
136
|
+
intervention.approval > 0 ? `承認 ${intervention.approval}` : "",
|
|
137
|
+
intervention.expertReview > 0 ? `資格者の判断 ${intervention.expertReview}` : "",
|
|
138
|
+
].filter(Boolean);
|
|
139
|
+
const notes = [
|
|
140
|
+
quality.corrected > 0 ? `資格者が書き換え ${quality.corrected}` : "",
|
|
141
|
+
quality.stoppedByRule > 0 ? `規則が止めた ${quality.stoppedByRule}` : "",
|
|
142
|
+
coverage.seconds !== undefined ? `所要 ${coverage.seconds} 秒` : "",
|
|
143
|
+
].filter(Boolean);
|
|
144
|
+
const called =
|
|
145
|
+
attention.calls === 0
|
|
146
|
+
? coverage.finished
|
|
147
|
+
? "人を呼ばずに終わりました"
|
|
148
|
+
: "まだ人を呼んでいません"
|
|
149
|
+
: `人を呼んだ回数 ${attention.calls}(${asked.join("、")})`;
|
|
150
|
+
return [called, ...notes].join(" ");
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
/**
|
|
121
154
|
* How a reviewer is named on the screen. The role of a reviewer is a qualification the company
|
|
122
155
|
* names (tax-accountant), not one of the roles a person handles, so the word comes first and the
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// openshain: Reference CLI agent for the openshain runtime
|
|
2
|
+
|
|
3
|
+
// The sums live in core, where anything that reads a record can reach them.
|
|
4
|
+
export { summarizeUsage, type UsageSummary } from "@openshain/core";
|
|
2
5
|
export {
|
|
3
6
|
AGENTS_TEMPLATE,
|
|
4
7
|
CLAUDE_TEMPLATE,
|
|
@@ -27,5 +30,5 @@ export {
|
|
|
27
30
|
statusLabel,
|
|
28
31
|
} from "./labels.ts";
|
|
29
32
|
export { nextActor, progressLine, report } from "./report.ts";
|
|
30
|
-
export { formatUsage
|
|
33
|
+
export { formatUsage } from "./usage.ts";
|
|
31
34
|
export { findWorkspace } from "./workspace.ts";
|
package/src/labels.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const STATUS_LABELS: Record<WorkStatus, string> = {
|
|
|
7
7
|
waiting_input: "利用者の入力待ち",
|
|
8
8
|
waiting_approval: "承認待ち",
|
|
9
9
|
waiting_external: "外部の応答待ち",
|
|
10
|
+
paused: "停止中",
|
|
10
11
|
completed: "完了",
|
|
11
12
|
failed: "失敗",
|
|
12
13
|
cancelled: "取り消し",
|
|
@@ -31,6 +32,8 @@ export const REJECTION_LABELS: Record<ToolRejectionCode, string> = {
|
|
|
31
32
|
denied: "権限の表で不許可",
|
|
32
33
|
rejected_by_person: "承認されなかった",
|
|
33
34
|
path_changed: "承認したときと書き込み先が変わった",
|
|
35
|
+
base_changed: "承認したときからファイルが変わった",
|
|
36
|
+
not_read: "読まずに置き換えようとした",
|
|
34
37
|
out_of_range: "範囲の外",
|
|
35
38
|
};
|
|
36
39
|
|
package/src/report.ts
CHANGED
|
@@ -2,12 +2,13 @@ import {
|
|
|
2
2
|
type AnyEvent,
|
|
3
3
|
type Event,
|
|
4
4
|
pendingQuestions,
|
|
5
|
+
summarizeUsage,
|
|
5
6
|
type ToolContent,
|
|
6
7
|
type Work,
|
|
7
8
|
} from "@openshain/core";
|
|
8
9
|
import { describeInput, truncate } from "./format.ts";
|
|
9
10
|
import { failureLabel, rejectionLabel, statusLabel } from "./labels.ts";
|
|
10
|
-
import { formatUsage
|
|
11
|
+
import { formatUsage } from "./usage.ts";
|
|
11
12
|
|
|
12
13
|
/** One line for a tool call, a rejection or a failure; nothing for the other events. `names` maps call ids to tool names. */
|
|
13
14
|
export function progressLine(event: AnyEvent, names: Map<string, string>): string | undefined {
|
package/src/tui/controller.ts
CHANGED
|
@@ -417,7 +417,10 @@ export async function createController(options: ControllerOptions): Promise<Cont
|
|
|
417
417
|
case "aborted":
|
|
418
418
|
return stopped(result.work);
|
|
419
419
|
case "max_tokens":
|
|
420
|
-
|
|
420
|
+
// The answer ran into the limit mid-sentence, so a work it opened holds nothing yet.
|
|
421
|
+
return result.work
|
|
422
|
+
? `返答が長さの上限で切れました。${result.work} は途中のまま残っています。/work resume ${result.work} で続けられるようにします。一度に扱う量を分けて依頼すると、上限に当たらずに終わります。`
|
|
423
|
+
: "返答が長さの上限で切れました。";
|
|
421
424
|
case "refusal":
|
|
422
425
|
return "社員エージェントが続けられないと言っています。";
|
|
423
426
|
case "model_error":
|
package/src/usage.ts
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export interface UsageSummary {
|
|
4
|
-
modelCalls: number;
|
|
5
|
-
toolCalls: number;
|
|
6
|
-
inputTokens: number;
|
|
7
|
-
/** The part of inputTokens a prompt cache served. */
|
|
8
|
-
cachedInputTokens: number;
|
|
9
|
-
outputTokens: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** Totals over a work's events: calls and tokens. */
|
|
13
|
-
export function summarizeUsage(events: AnyEvent[]): UsageSummary {
|
|
14
|
-
const summary: UsageSummary = {
|
|
15
|
-
modelCalls: 0,
|
|
16
|
-
toolCalls: 0,
|
|
17
|
-
inputTokens: 0,
|
|
18
|
-
cachedInputTokens: 0,
|
|
19
|
-
outputTokens: 0,
|
|
20
|
-
};
|
|
21
|
-
for (const event of events) {
|
|
22
|
-
if (event.type === "model.requested") summary.modelCalls += 1;
|
|
23
|
-
if (event.type === "usage.recorded") {
|
|
24
|
-
const { payload } = event as Event<"usage.recorded">;
|
|
25
|
-
if (payload.kind === "model_inference") {
|
|
26
|
-
summary.inputTokens += payload.usage.inputTokens;
|
|
27
|
-
summary.cachedInputTokens += payload.usage.cachedInputTokens ?? 0;
|
|
28
|
-
summary.outputTokens += payload.usage.outputTokens;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
summary.toolCalls = countToolCalls(events);
|
|
33
|
-
return summary;
|
|
34
|
-
}
|
|
1
|
+
import type { UsageSummary } from "@openshain/core";
|
|
35
2
|
|
|
36
3
|
export function formatUsage(summary: UsageSummary): string {
|
|
37
4
|
if (summary.modelCalls === 0 && summary.inputTokens === 0 && summary.outputTokens === 0) {
|