openshain 0.8.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 +1 -0
- package/dist/report.js +2 -2
- 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 +1 -0
- package/src/report.ts +2 -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
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/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
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/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) {
|