@nt-ai-lab/deterministic-agent-workflow-cli 0.1.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/features/workflow-cli/entrypoint/workflow-cli.d.ts +4 -0
- package/dist/features/workflow-cli/entrypoint/workflow-cli.js +81 -0
- package/dist/features/workflow-runner/entrypoint/workflow-runner.d.ts +5 -0
- package/dist/features/workflow-runner/entrypoint/workflow-runner.js +216 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +11 -0
- package/dist/platform/domain/argument-parser-types.d.ts +13 -0
- package/dist/platform/domain/argument-parser-types.js +1 -0
- package/dist/platform/domain/command-definition.d.ts +26 -0
- package/dist/platform/domain/command-definition.js +4 -0
- package/dist/platform/domain/extract-field.d.ts +2 -0
- package/dist/platform/domain/extract-field.js +13 -0
- package/dist/platform/domain/platform-context.d.ts +8 -0
- package/dist/platform/domain/platform-context.js +1 -0
- package/dist/platform/domain/pre-tool-use-handler.d.ts +21 -0
- package/dist/platform/domain/pre-tool-use-handler.js +44 -0
- package/dist/platform/domain/workflow-cli-types.d.ts +21 -0
- package/dist/platform/domain/workflow-cli-types.js +1 -0
- package/dist/platform/domain/workflow-runner-types.d.ts +25 -0
- package/dist/platform/domain/workflow-runner-types.js +1 -0
- package/dist/platform/infra/cli/input/argument-parser.d.ts +9 -0
- package/dist/platform/infra/cli/input/argument-parser.js +93 -0
- package/dist/platform/infra/cli/presentation/hook-output.d.ts +4 -0
- package/dist/platform/infra/cli/presentation/hook-output.js +14 -0
- package/dist/platform/infra/external-clients/claude-hooks/hook-schemas.d.ts +105 -0
- package/dist/platform/infra/external-clients/claude-hooks/hook-schemas.js +19 -0
- package/dist/platform/infra/external-clients/git/repository-name.d.ts +2 -0
- package/dist/platform/infra/external-clients/git/repository-name.js +22 -0
- package/dist/platform/infra/external-clients/process/default-process-deps.d.ts +3 -0
- package/dist/platform/infra/external-clients/process/default-process-deps.js +15 -0
- package/dist/shell/exit-codes.d.ts +3 -0
- package/dist/shell/exit-codes.js +3 -0
- package/package.json +22 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BaseWorkflowState, RehydratableWorkflow } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { WorkflowCliConfig } from '../../../platform/domain/workflow-cli-types';
|
|
3
|
+
/** @riviere-role cli-entrypoint */
|
|
4
|
+
export declare function createWorkflowCli<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState, TDeps>(config: WorkflowCliConfig<TWorkflow, TState, TDeps>): void;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { createWorkflowRunner } from '../../workflow-runner/entrypoint/workflow-runner.js';
|
|
3
|
+
import { getRepositoryName } from '../../../platform/infra/external-clients/git/repository-name.js';
|
|
4
|
+
function buildReadEnvVar(getEnv) {
|
|
5
|
+
return function readEnvVar(name) {
|
|
6
|
+
const value = getEnv(name);
|
|
7
|
+
if (value === undefined || value === '') {
|
|
8
|
+
throw new TypeError(`Missing required environment variable: ${name}`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** @riviere-role cli-entrypoint */
|
|
14
|
+
export function createWorkflowCli(config) {
|
|
15
|
+
const { processDeps } = config;
|
|
16
|
+
const readEnvVar = buildReadEnvVar(processDeps.getEnv);
|
|
17
|
+
const pluginRoot = readEnvVar('CLAUDE_PLUGIN_ROOT');
|
|
18
|
+
const getSessionId = () => readEnvVar('CLAUDE_SESSION_ID');
|
|
19
|
+
const configuredWorkflowEventsDbPath = processDeps.getEnv('WORKFLOW_EVENTS_DB');
|
|
20
|
+
const workflowEventsDbPath = configuredWorkflowEventsDbPath !== undefined && configuredWorkflowEventsDbPath !== ''
|
|
21
|
+
? configuredWorkflowEventsDbPath
|
|
22
|
+
: join(readEnvVar('HOME'), '.workflow-events.db');
|
|
23
|
+
const store = processDeps.buildStore(workflowEventsDbPath);
|
|
24
|
+
const now = () => new Date().toISOString();
|
|
25
|
+
const platformCtx = {
|
|
26
|
+
getPluginRoot: () => pluginRoot,
|
|
27
|
+
now,
|
|
28
|
+
getSessionId,
|
|
29
|
+
store,
|
|
30
|
+
};
|
|
31
|
+
const engineDeps = {
|
|
32
|
+
store,
|
|
33
|
+
getPluginRoot: () => pluginRoot,
|
|
34
|
+
getEnvFilePath: () => join(readEnvVar('HOME'), '.claude', 'claude.env'),
|
|
35
|
+
getRepositoryName: () => getRepositoryName(process.cwd()),
|
|
36
|
+
readFile: processDeps.readFile,
|
|
37
|
+
appendToFile: processDeps.appendToFile,
|
|
38
|
+
now,
|
|
39
|
+
transcriptReader: config.transcriptReader,
|
|
40
|
+
};
|
|
41
|
+
const workflowDeps = config.buildWorkflowDeps(platformCtx);
|
|
42
|
+
const readStdin = () => processDeps.readFile('/dev/stdin');
|
|
43
|
+
const errorLogPath = join(pluginRoot, 'error.log');
|
|
44
|
+
try {
|
|
45
|
+
const args = processDeps.getArgv().slice(2);
|
|
46
|
+
const command = args[0];
|
|
47
|
+
if (args.length > 0 && config.customRouter !== undefined) {
|
|
48
|
+
const custom = config.customRouter(command, args, platformCtx);
|
|
49
|
+
if (custom !== undefined) {
|
|
50
|
+
writeRunnerResult(processDeps, custom);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const runner = createWorkflowRunner(config);
|
|
55
|
+
const result = runner(args, engineDeps, workflowDeps, {
|
|
56
|
+
readStdin,
|
|
57
|
+
getSessionId,
|
|
58
|
+
});
|
|
59
|
+
if (result.output) {
|
|
60
|
+
processDeps.writeStdout(result.output);
|
|
61
|
+
}
|
|
62
|
+
processDeps.exit(result.exitCode);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = `[${new Date().toISOString()}] ERROR: ${String(error)}\n`;
|
|
66
|
+
processDeps.writeStderr(message);
|
|
67
|
+
try {
|
|
68
|
+
processDeps.appendToFile(errorLogPath, message);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Ignore write failures to error log.
|
|
72
|
+
}
|
|
73
|
+
processDeps.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function writeRunnerResult(processDeps, result) {
|
|
77
|
+
if (result.output !== '') {
|
|
78
|
+
processDeps.writeStdout(result.output);
|
|
79
|
+
}
|
|
80
|
+
processDeps.exit(result.exitCode);
|
|
81
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BaseWorkflowState, RehydratableWorkflow, WorkflowEngineDeps } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { RunnerOptions, RunnerResult, WorkflowRunnerConfig } from '../../../platform/domain/workflow-runner-types';
|
|
3
|
+
export type { PreToolUseHandlerFn } from '../../../platform/domain/pre-tool-use-handler';
|
|
4
|
+
/** @riviere-role cli-entrypoint */
|
|
5
|
+
export declare function createWorkflowRunner<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TDeps, TStateName extends string = string, TOperation extends string = string>(config: WorkflowRunnerConfig<TWorkflow, TState, TDeps, TStateName, TOperation>): (args: readonly string[], engineDeps: WorkflowEngineDeps, workflowDeps: TDeps, options?: RunnerOptions) => RunnerResult;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { WorkflowEngine } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import { EXIT_ALLOW, EXIT_BLOCK, EXIT_ERROR } from '../../../shell/exit-codes.js';
|
|
3
|
+
import { formatContextInjection, formatDenyDecision } from '../../../platform/infra/cli/presentation/hook-output.js';
|
|
4
|
+
import { hookCommonInputSchema, preToolUseInputSchema, subagentStartInputSchema, teammateIdleInputSchema, } from '../../../platform/infra/external-clients/claude-hooks/hook-schemas.js';
|
|
5
|
+
import { createPreToolUseHandler } from '../../../platform/domain/pre-tool-use-handler.js';
|
|
6
|
+
import { getRepositoryName } from '../../../platform/infra/external-clients/git/repository-name.js';
|
|
7
|
+
function resolvePreToolUseHandler(config) {
|
|
8
|
+
const hasPolicy = config.bashForbidden !== undefined || config.isWriteAllowed !== undefined || config.customGates !== undefined;
|
|
9
|
+
if (config.preToolUseHandler !== undefined) {
|
|
10
|
+
if (hasPolicy) {
|
|
11
|
+
throw new TypeError('WorkflowRunnerConfig: preToolUseHandler is mutually exclusive with bashForbidden/isWriteAllowed/customGates. Provide either policy fields (default path) or a custom handler (escape hatch), not both.');
|
|
12
|
+
}
|
|
13
|
+
return config.preToolUseHandler;
|
|
14
|
+
}
|
|
15
|
+
if (config.bashForbidden === undefined && config.isWriteAllowed === undefined) {
|
|
16
|
+
if (config.customGates !== undefined) {
|
|
17
|
+
throw new TypeError('WorkflowRunnerConfig: customGates requires bashForbidden and isWriteAllowed to also be set.');
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (config.bashForbidden === undefined || config.isWriteAllowed === undefined) {
|
|
22
|
+
throw new TypeError('WorkflowRunnerConfig: bashForbidden and isWriteAllowed must be provided together.');
|
|
23
|
+
}
|
|
24
|
+
if (config.customGates === undefined) {
|
|
25
|
+
return createPreToolUseHandler({
|
|
26
|
+
bashForbidden: config.bashForbidden,
|
|
27
|
+
isWriteAllowed: config.isWriteAllowed
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return createPreToolUseHandler({
|
|
31
|
+
bashForbidden: config.bashForbidden,
|
|
32
|
+
isWriteAllowed: config.isWriteAllowed,
|
|
33
|
+
customGates: config.customGates,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function engineResultToRunnerResult(result) {
|
|
37
|
+
switch (result.type) {
|
|
38
|
+
case 'success': return {
|
|
39
|
+
output: result.output,
|
|
40
|
+
exitCode: EXIT_ALLOW
|
|
41
|
+
};
|
|
42
|
+
case 'blocked': return {
|
|
43
|
+
output: result.output,
|
|
44
|
+
exitCode: EXIT_BLOCK
|
|
45
|
+
};
|
|
46
|
+
case 'error': return {
|
|
47
|
+
output: result.output,
|
|
48
|
+
exitCode: EXIT_ERROR
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function parseArgs(argParsers, args, routeName) {
|
|
53
|
+
const values = [];
|
|
54
|
+
for (const [index, parser] of (argParsers ?? []).entries()) {
|
|
55
|
+
const result = parser.parse(args, index + 1, routeName);
|
|
56
|
+
if (!result.ok)
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
message: result.message
|
|
60
|
+
};
|
|
61
|
+
values.push(result.value);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
values
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function assertSessionId(values) {
|
|
69
|
+
const id = values[0];
|
|
70
|
+
if (typeof id !== 'string')
|
|
71
|
+
throw new TypeError('session-id argument must be a string');
|
|
72
|
+
return id;
|
|
73
|
+
}
|
|
74
|
+
function assertTarget(values) {
|
|
75
|
+
const target = values[1];
|
|
76
|
+
if (typeof target !== 'string')
|
|
77
|
+
throw new TypeError('target argument must be a string');
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
/** @riviere-role cli-entrypoint */
|
|
81
|
+
export function createWorkflowRunner(config) {
|
|
82
|
+
const resolvedHandler = resolvePreToolUseHandler(config);
|
|
83
|
+
return (args, engineDeps, workflowDeps, options) => {
|
|
84
|
+
const engine = new WorkflowEngine(config.workflowDefinition, engineDeps, workflowDeps);
|
|
85
|
+
if (args.length > 0) {
|
|
86
|
+
return handleRoute(engine, config, args, args[0], options?.getSessionId, options?.getSessionTranscriptPath, options?.getSessionRepository);
|
|
87
|
+
}
|
|
88
|
+
if (options?.readStdin === undefined)
|
|
89
|
+
return {
|
|
90
|
+
output: 'No command and no stdin available',
|
|
91
|
+
exitCode: EXIT_ERROR
|
|
92
|
+
};
|
|
93
|
+
return handleHook(engine, resolvedHandler, options.readStdin);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function handleRoute(engine, config, args, routeName, getSessionId, getSessionTranscriptPath, getSessionRepository) {
|
|
97
|
+
const routeDef = Object.hasOwn(config.routes, routeName) ? config.routes[routeName] : undefined;
|
|
98
|
+
if (routeDef === undefined)
|
|
99
|
+
return {
|
|
100
|
+
output: `Unknown command: ${routeName}`,
|
|
101
|
+
exitCode: EXIT_ERROR
|
|
102
|
+
};
|
|
103
|
+
const parsedArgs = parseArgs(routeDef.args, args, routeName);
|
|
104
|
+
if (!parsedArgs.ok)
|
|
105
|
+
return {
|
|
106
|
+
output: parsedArgs.message,
|
|
107
|
+
exitCode: EXIT_ERROR
|
|
108
|
+
};
|
|
109
|
+
const resolveSessionId = () => getSessionId === undefined ? assertSessionId(parsedArgs.values) : getSessionId();
|
|
110
|
+
const argsAfterSessionId = () => getSessionId === undefined ? parsedArgs.values.slice(1) : parsedArgs.values;
|
|
111
|
+
const resolveTarget = () => {
|
|
112
|
+
if (getSessionId === undefined)
|
|
113
|
+
return assertTarget(parsedArgs.values);
|
|
114
|
+
const target = parsedArgs.values[0];
|
|
115
|
+
if (typeof target !== 'string')
|
|
116
|
+
throw new TypeError('target argument must be a string');
|
|
117
|
+
return target;
|
|
118
|
+
};
|
|
119
|
+
switch (routeDef.type) {
|
|
120
|
+
case 'session-start': {
|
|
121
|
+
const transcriptPath = getSessionTranscriptPath === undefined ? '' : getSessionTranscriptPath();
|
|
122
|
+
return engineResultToRunnerResult(engine.startSession(resolveSessionId(), transcriptPath, getSessionRepository?.()));
|
|
123
|
+
}
|
|
124
|
+
case 'transition':
|
|
125
|
+
return engineResultToRunnerResult(engine.transition(resolveSessionId(), config.workflowDefinition.stateSchema.parse(resolveTarget())));
|
|
126
|
+
case 'transaction':
|
|
127
|
+
return engineResultToRunnerResult(engine.transaction(resolveSessionId(), routeName, (workflow) => routeDef.handler(workflow, ...argsAfterSessionId())));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function handleHook(engine, resolvedHandler, readStdin) {
|
|
131
|
+
const stdin = readStdin();
|
|
132
|
+
const hookInput = JSON.parse(stdin);
|
|
133
|
+
const commonParse = hookCommonInputSchema.safeParse(hookInput);
|
|
134
|
+
if (!commonParse.success)
|
|
135
|
+
return {
|
|
136
|
+
output: `Invalid hook input: ${commonParse.error.message}`,
|
|
137
|
+
exitCode: EXIT_ERROR
|
|
138
|
+
};
|
|
139
|
+
const common = commonParse.data;
|
|
140
|
+
if (common.hook_event_name === 'SessionStart') {
|
|
141
|
+
const result = engine.startSession(common.session_id, common.transcript_path, getRepositoryName(common.cwd));
|
|
142
|
+
engine.persistSessionId(common.session_id);
|
|
143
|
+
return engineResultToRunnerResult(result);
|
|
144
|
+
}
|
|
145
|
+
if (!engine.hasSession(common.session_id))
|
|
146
|
+
return {
|
|
147
|
+
output: '',
|
|
148
|
+
exitCode: EXIT_ALLOW
|
|
149
|
+
};
|
|
150
|
+
switch (common.hook_event_name) {
|
|
151
|
+
case 'PreToolUse': return handlePreToolUseHook(engine, resolvedHandler, stdin);
|
|
152
|
+
case 'SubagentStart': return handleSubagentStartHook(engine, stdin);
|
|
153
|
+
case 'TeammateIdle': return handleTeammateIdleHook(engine, stdin);
|
|
154
|
+
default: return {
|
|
155
|
+
output: '',
|
|
156
|
+
exitCode: EXIT_ALLOW
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function handlePreToolUseHook(engine, resolvedHandler, stdin) {
|
|
161
|
+
const hookInput = JSON.parse(stdin);
|
|
162
|
+
const toolParse = preToolUseInputSchema.safeParse(hookInput);
|
|
163
|
+
if (!toolParse.success)
|
|
164
|
+
return {
|
|
165
|
+
output: `Invalid pre-tool-use input: ${toolParse.error.message}`,
|
|
166
|
+
exitCode: EXIT_ERROR
|
|
167
|
+
};
|
|
168
|
+
return handlePreToolUse(engine, resolvedHandler, toolParse.data);
|
|
169
|
+
}
|
|
170
|
+
function handlePreToolUse(engine, resolvedHandler, input) {
|
|
171
|
+
if (resolvedHandler === undefined)
|
|
172
|
+
return {
|
|
173
|
+
output: '',
|
|
174
|
+
exitCode: EXIT_ALLOW
|
|
175
|
+
};
|
|
176
|
+
const result = resolvedHandler(engine, input.session_id, input.tool_name, input.tool_input);
|
|
177
|
+
if (result.type === 'blocked')
|
|
178
|
+
return {
|
|
179
|
+
output: formatDenyDecision(result.output),
|
|
180
|
+
exitCode: EXIT_BLOCK
|
|
181
|
+
};
|
|
182
|
+
return engineResultToRunnerResult(result);
|
|
183
|
+
}
|
|
184
|
+
function handleSubagentStartHook(engine, stdin) {
|
|
185
|
+
const hookInput = JSON.parse(stdin);
|
|
186
|
+
const parsed = subagentStartInputSchema.safeParse(hookInput);
|
|
187
|
+
if (!parsed.success)
|
|
188
|
+
return {
|
|
189
|
+
output: `Invalid subagent-start input: ${parsed.error.message}`,
|
|
190
|
+
exitCode: EXIT_ERROR
|
|
191
|
+
};
|
|
192
|
+
const input = parsed.data;
|
|
193
|
+
const result = engine.transaction(input.session_id, 'register-agent', (workflow) => workflow.registerAgent(input.agent_type, input.agent_id));
|
|
194
|
+
return {
|
|
195
|
+
output: formatContextInjection(result.type === 'success' ? result.output : ''),
|
|
196
|
+
exitCode: EXIT_ALLOW
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function handleTeammateIdleHook(engine, stdin) {
|
|
200
|
+
const hookInput = JSON.parse(stdin);
|
|
201
|
+
const parsed = teammateIdleInputSchema.safeParse(hookInput);
|
|
202
|
+
if (!parsed.success)
|
|
203
|
+
return {
|
|
204
|
+
output: `Invalid teammate-idle input: ${parsed.error.message}`,
|
|
205
|
+
exitCode: EXIT_ERROR
|
|
206
|
+
};
|
|
207
|
+
const input = parsed.data;
|
|
208
|
+
const agentName = resolveTeammateName(input.teammate_name);
|
|
209
|
+
return engineResultToRunnerResult(engine.transaction(input.session_id, 'check-idle', (workflow) => workflow.handleTeammateIdle(agentName)));
|
|
210
|
+
}
|
|
211
|
+
function resolveTeammateName(teammateName) {
|
|
212
|
+
if (teammateName === undefined) {
|
|
213
|
+
return '';
|
|
214
|
+
}
|
|
215
|
+
return teammateName;
|
|
216
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { arg } from './platform/infra/cli/input/argument-parser';
|
|
2
|
+
export type { ArgParser, ArgResult, } from './platform/domain/argument-parser-types';
|
|
3
|
+
export { extractField } from './platform/domain/extract-field';
|
|
4
|
+
export { defineRoutes } from './platform/domain/command-definition';
|
|
5
|
+
export type { RouteDefinition, RouteMap, } from './platform/domain/command-definition';
|
|
6
|
+
export { createWorkflowRunner } from './features/workflow-runner/entrypoint/workflow-runner';
|
|
7
|
+
export type { RunnerResult, RunnerOptions, WorkflowRunnerConfig, } from './platform/domain/workflow-runner-types';
|
|
8
|
+
export type { PreToolUseHandlerFn } from './platform/domain/pre-tool-use-handler';
|
|
9
|
+
export { createPreToolUseHandler } from './platform/domain/pre-tool-use-handler';
|
|
10
|
+
export type { PreToolUseHandlerConfig, CustomPreToolUseGate, } from './platform/domain/pre-tool-use-handler';
|
|
11
|
+
export { formatContextInjection, formatDenyDecision, } from './platform/infra/cli/presentation/hook-output';
|
|
12
|
+
export { createWorkflowCli } from './features/workflow-cli/entrypoint/workflow-cli';
|
|
13
|
+
export type { ProcessDeps, WorkflowCliConfig, } from './platform/domain/workflow-cli-types';
|
|
14
|
+
export { createDefaultProcessDeps } from './platform/infra/external-clients/process/default-process-deps';
|
|
15
|
+
export type { PlatformContext } from './platform/domain/platform-context';
|
|
16
|
+
export { getRepositoryName } from './platform/infra/external-clients/git/repository-name';
|
|
17
|
+
export { EXIT_ALLOW, EXIT_BLOCK, EXIT_ERROR, } from './shell/exit-codes';
|
|
18
|
+
export { hookCommonInputSchema, preToolUseInputSchema, subagentStartInputSchema, teammateIdleInputSchema, } from './platform/infra/external-clients/claude-hooks/hook-schemas';
|
|
19
|
+
export type { PreToolUseInput, SubagentStartInput, TeammateIdleInput, } from './platform/infra/external-clients/claude-hooks/hook-schemas';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { arg } from './platform/infra/cli/input/argument-parser.js';
|
|
2
|
+
export { extractField } from './platform/domain/extract-field.js';
|
|
3
|
+
export { defineRoutes } from './platform/domain/command-definition.js';
|
|
4
|
+
export { createWorkflowRunner } from './features/workflow-runner/entrypoint/workflow-runner.js';
|
|
5
|
+
export { createPreToolUseHandler } from './platform/domain/pre-tool-use-handler.js';
|
|
6
|
+
export { formatContextInjection, formatDenyDecision, } from './platform/infra/cli/presentation/hook-output.js';
|
|
7
|
+
export { createWorkflowCli } from './features/workflow-cli/entrypoint/workflow-cli.js';
|
|
8
|
+
export { createDefaultProcessDeps } from './platform/infra/external-clients/process/default-process-deps.js';
|
|
9
|
+
export { getRepositoryName } from './platform/infra/external-clients/git/repository-name.js';
|
|
10
|
+
export { EXIT_ALLOW, EXIT_BLOCK, EXIT_ERROR, } from './shell/exit-codes.js';
|
|
11
|
+
export { hookCommonInputSchema, preToolUseInputSchema, subagentStartInputSchema, teammateIdleInputSchema, } from './platform/infra/external-clients/claude-hooks/hook-schemas.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @riviere-role value-object */
|
|
2
|
+
export type ArgResult<T> = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly value: T;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
};
|
|
9
|
+
/** @riviere-role value-object */
|
|
10
|
+
export type ArgParser<T> = {
|
|
11
|
+
readonly parse: (args: readonly string[], position: number, commandName: string) => ArgResult<T>;
|
|
12
|
+
readonly optional: () => ArgParser<T | undefined>;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PreconditionResult } from '@nt-ai-lab/deterministic-agent-workflow-dsl';
|
|
2
|
+
import type { ArgParser } from './argument-parser-types';
|
|
3
|
+
type RouteHandler<TWorkflow> = (workflow: TWorkflow, ...parsedArgs: readonly unknown[]) => PreconditionResult;
|
|
4
|
+
type TransactionRoute<TWorkflow> = {
|
|
5
|
+
readonly type: 'transaction';
|
|
6
|
+
readonly args?: readonly ArgParser<unknown>[];
|
|
7
|
+
readonly handler: RouteHandler<TWorkflow>;
|
|
8
|
+
};
|
|
9
|
+
type TransitionRoute = {
|
|
10
|
+
readonly type: 'transition';
|
|
11
|
+
readonly args?: readonly ArgParser<unknown>[];
|
|
12
|
+
};
|
|
13
|
+
type SessionStartRoute = {
|
|
14
|
+
readonly type: 'session-start';
|
|
15
|
+
readonly args?: readonly ArgParser<unknown>[];
|
|
16
|
+
};
|
|
17
|
+
type RouteStateMarker<TState> = {
|
|
18
|
+
readonly __stateBrand?: TState;
|
|
19
|
+
};
|
|
20
|
+
/** @riviere-role value-object */
|
|
21
|
+
export type RouteDefinition<TWorkflow, TState> = (TransactionRoute<TWorkflow> | TransitionRoute | SessionStartRoute) & RouteStateMarker<TState>;
|
|
22
|
+
/** @riviere-role value-object */
|
|
23
|
+
export type RouteMap<TWorkflow, TState> = Record<string, RouteDefinition<TWorkflow, TState>>;
|
|
24
|
+
/** @riviere-role domain-service */
|
|
25
|
+
export declare function defineRoutes<TWorkflow, TState>(routes: RouteMap<TWorkflow, TState>): RouteMap<TWorkflow, TState>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @riviere-role domain-service */
|
|
2
|
+
export function extractField(fieldName) {
|
|
3
|
+
return (toolInput) => {
|
|
4
|
+
const value = toolInput[fieldName];
|
|
5
|
+
if (value === undefined || value === null) {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
if (typeof value !== 'string') {
|
|
9
|
+
throw new TypeError(`Expected '${fieldName}' to be a string, got ${typeof value}`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorkflowEventStore } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
/** @riviere-role value-object */
|
|
3
|
+
export type PlatformContext = {
|
|
4
|
+
readonly getPluginRoot: () => string;
|
|
5
|
+
readonly now: () => string;
|
|
6
|
+
readonly getSessionId: () => string;
|
|
7
|
+
readonly store: WorkflowEventStore;
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BaseWorkflowState, EngineResult, RehydratableWorkflow, WorkflowEngine } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { BashForbiddenConfig } from '@nt-ai-lab/deterministic-agent-workflow-dsl';
|
|
3
|
+
/** @riviere-role value-object */
|
|
4
|
+
export type PreToolUseHandlerFn<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TDeps, TStateName extends string = string, TOperation extends string = string> = (engine: WorkflowEngine<TWorkflow, TState, TDeps, TStateName, TOperation>, sessionId: string, toolName: string, toolInput: Record<string, unknown>) => EngineResult;
|
|
5
|
+
/** @riviere-role value-object */
|
|
6
|
+
export type CustomPreToolUseGate<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TStateName extends string = string> = {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly check: (workflow: TWorkflow, ctx: {
|
|
9
|
+
readonly toolName: string;
|
|
10
|
+
readonly filePath: string;
|
|
11
|
+
readonly command: string;
|
|
12
|
+
}) => true | string;
|
|
13
|
+
};
|
|
14
|
+
/** @riviere-role value-object */
|
|
15
|
+
export type PreToolUseHandlerConfig<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TStateName extends string = string> = {
|
|
16
|
+
readonly bashForbidden: BashForbiddenConfig;
|
|
17
|
+
readonly isWriteAllowed: (filePath: string, state: TState) => boolean;
|
|
18
|
+
readonly customGates?: readonly CustomPreToolUseGate<TWorkflow, TState, TStateName>[];
|
|
19
|
+
};
|
|
20
|
+
/** @riviere-role domain-service */
|
|
21
|
+
export declare function createPreToolUseHandler<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TDeps, TStateName extends string = string, TOperation extends string = string>(config: PreToolUseHandlerConfig<TWorkflow, TState, TStateName>): PreToolUseHandlerFn<TWorkflow, TState, TDeps, TStateName, TOperation>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** @riviere-role domain-service */
|
|
2
|
+
export function createPreToolUseHandler(config) {
|
|
3
|
+
return (engine, sessionId, toolName, toolInput) => {
|
|
4
|
+
const filePath = extractFilePath(toolInput);
|
|
5
|
+
const command = extractCommand(toolInput);
|
|
6
|
+
const ctx = {
|
|
7
|
+
toolName,
|
|
8
|
+
filePath,
|
|
9
|
+
command
|
|
10
|
+
};
|
|
11
|
+
for (const gate of config.customGates ?? []) {
|
|
12
|
+
const result = engine.transaction(sessionId, `hook:${gate.name}`, (workflow) => {
|
|
13
|
+
const check = gate.check(workflow, ctx);
|
|
14
|
+
if (check === true)
|
|
15
|
+
return { pass: true };
|
|
16
|
+
return {
|
|
17
|
+
pass: false,
|
|
18
|
+
reason: check
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
if (result.type === 'blocked')
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
const writeCheck = engine.checkWrite(sessionId, toolName, filePath, config.isWriteAllowed);
|
|
25
|
+
if (writeCheck.type === 'blocked')
|
|
26
|
+
return writeCheck;
|
|
27
|
+
return engine.checkBash(sessionId, toolName, command, config.bashForbidden);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function extractFilePath(toolInput) {
|
|
31
|
+
return resolveStringField(toolInput['file_path'])
|
|
32
|
+
|| resolveStringField(toolInput['path'])
|
|
33
|
+
|| resolveStringField(toolInput['pattern']);
|
|
34
|
+
}
|
|
35
|
+
function extractCommand(toolInput) {
|
|
36
|
+
return resolveStringField(toolInput['command']);
|
|
37
|
+
}
|
|
38
|
+
function resolveStringField(value) {
|
|
39
|
+
if (value === undefined || value === null)
|
|
40
|
+
return '';
|
|
41
|
+
if (typeof value === 'string')
|
|
42
|
+
return value;
|
|
43
|
+
throw new TypeError(`Expected string or undefined in tool_input field. Got ${typeof value}: ${String(value)}`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BaseWorkflowState, RehydratableWorkflow, TranscriptReader, WorkflowEventStore } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { PlatformContext } from './platform-context';
|
|
3
|
+
import type { RunnerResult, WorkflowRunnerConfig } from './workflow-runner-types';
|
|
4
|
+
/** @riviere-role value-object */
|
|
5
|
+
export type ProcessDeps = {
|
|
6
|
+
readonly getEnv: (name: string) => string | undefined;
|
|
7
|
+
readonly exit: (code: number) => void;
|
|
8
|
+
readonly writeStdout: (s: string) => void;
|
|
9
|
+
readonly writeStderr: (s: string) => void;
|
|
10
|
+
readonly getArgv: () => readonly string[];
|
|
11
|
+
readonly readFile: (path: string) => string;
|
|
12
|
+
readonly appendToFile: (path: string, content: string) => void;
|
|
13
|
+
readonly buildStore: (dbPath: string) => WorkflowEventStore;
|
|
14
|
+
};
|
|
15
|
+
/** @riviere-role value-object */
|
|
16
|
+
export type WorkflowCliConfig<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState, TDeps> = WorkflowRunnerConfig<TWorkflow, TState, TDeps> & {
|
|
17
|
+
readonly buildWorkflowDeps: (platform: PlatformContext) => TDeps;
|
|
18
|
+
readonly customRouter?: (command: string, args: readonly string[], platform: PlatformContext) => RunnerResult | undefined;
|
|
19
|
+
readonly processDeps: ProcessDeps;
|
|
20
|
+
readonly transcriptReader: TranscriptReader;
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BaseWorkflowState, RehydratableWorkflow, WorkflowDefinition } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { BashForbiddenConfig } from '@nt-ai-lab/deterministic-agent-workflow-dsl';
|
|
3
|
+
import type { RouteMap } from './command-definition';
|
|
4
|
+
import type { CustomPreToolUseGate, PreToolUseHandlerFn } from './pre-tool-use-handler';
|
|
5
|
+
/** @riviere-role value-object */
|
|
6
|
+
export type RunnerResult = {
|
|
7
|
+
readonly output: string;
|
|
8
|
+
readonly exitCode: number;
|
|
9
|
+
};
|
|
10
|
+
/** @riviere-role value-object */
|
|
11
|
+
export type RunnerOptions = {
|
|
12
|
+
readonly readStdin?: () => string;
|
|
13
|
+
readonly getSessionId?: () => string;
|
|
14
|
+
readonly getSessionTranscriptPath?: () => string;
|
|
15
|
+
readonly getSessionRepository?: () => string | undefined;
|
|
16
|
+
};
|
|
17
|
+
/** @riviere-role value-object */
|
|
18
|
+
export type WorkflowRunnerConfig<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TDeps, TStateName extends string = string, TOperation extends string = string> = {
|
|
19
|
+
readonly workflowDefinition: WorkflowDefinition<TWorkflow, TState, TDeps, TStateName, TOperation>;
|
|
20
|
+
readonly routes: RouteMap<TWorkflow, TState>;
|
|
21
|
+
readonly bashForbidden?: BashForbiddenConfig;
|
|
22
|
+
readonly isWriteAllowed?: (filePath: string, state: TState) => boolean;
|
|
23
|
+
readonly customGates?: readonly CustomPreToolUseGate<TWorkflow, TState, TStateName>[];
|
|
24
|
+
readonly preToolUseHandler?: PreToolUseHandlerFn<TWorkflow, TState, TDeps, TStateName, TOperation>;
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ZodType } from 'zod';
|
|
2
|
+
import type { ArgParser } from '../../../domain/argument-parser-types';
|
|
3
|
+
/** @riviere-role cli-input-validator */
|
|
4
|
+
export declare const arg: {
|
|
5
|
+
number: (name: string) => ArgParser<number>;
|
|
6
|
+
string: (name: string) => ArgParser<string>;
|
|
7
|
+
rest: () => ArgParser<readonly string[]>;
|
|
8
|
+
state: <T extends string>(name: string, schema: ZodType<T>) => ArgParser<T>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function makeOptional(parser) {
|
|
2
|
+
return {
|
|
3
|
+
parse: (args, position, commandName) => {
|
|
4
|
+
if (position >= args.length) {
|
|
5
|
+
return {
|
|
6
|
+
ok: true,
|
|
7
|
+
value: undefined
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return parser.parse(args, position, commandName);
|
|
11
|
+
},
|
|
12
|
+
optional: () => makeOptional(parser),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** @riviere-role cli-input-validator */
|
|
16
|
+
export const arg = {
|
|
17
|
+
number: (name) => ({
|
|
18
|
+
parse: (args, position, commandName) => {
|
|
19
|
+
if (position >= args.length) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
message: `${commandName}: missing required argument <${name}>`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const raw = args[position];
|
|
26
|
+
const parsed = Number.parseInt(raw, 10);
|
|
27
|
+
if (Number.isNaN(parsed)) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
message: `${commandName}: not a valid number: '${raw}'`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
value: parsed
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
optional: function () {
|
|
39
|
+
return makeOptional(this);
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
string: (name) => ({
|
|
43
|
+
parse: (args, position, commandName) => {
|
|
44
|
+
if (position >= args.length) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
message: `${commandName}: missing required argument <${name}>`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
value: args[position]
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
optional: function () {
|
|
56
|
+
return makeOptional(this);
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
rest: () => ({
|
|
60
|
+
parse: (args, position) => ({
|
|
61
|
+
ok: true,
|
|
62
|
+
value: args.slice(position)
|
|
63
|
+
}),
|
|
64
|
+
optional: function () {
|
|
65
|
+
return makeOptional(this);
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
state: (name, schema) => ({
|
|
69
|
+
parse: (args, position, commandName) => {
|
|
70
|
+
if (position >= args.length) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
message: `${commandName}: missing required argument <${name}>`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const raw = args[position];
|
|
77
|
+
const result = schema.safeParse(raw);
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
message: `${commandName}: invalid state '${raw}'`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
value: result.data
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
optional: function () {
|
|
90
|
+
return makeOptional(this);
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @riviere-role cli-output-formatter */
|
|
2
|
+
export function formatDenyDecision(reason) {
|
|
3
|
+
return JSON.stringify({
|
|
4
|
+
hookSpecificOutput: {
|
|
5
|
+
hookEventName: 'PreToolUse',
|
|
6
|
+
permissionDecision: 'deny',
|
|
7
|
+
permissionDecisionReason: reason,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/** @riviere-role cli-output-formatter */
|
|
12
|
+
export function formatContextInjection(context) {
|
|
13
|
+
return JSON.stringify({ additionalContext: context });
|
|
14
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const hookCommonInputSchema: z.ZodObject<{
|
|
3
|
+
session_id: z.ZodString;
|
|
4
|
+
transcript_path: z.ZodString;
|
|
5
|
+
cwd: z.ZodString;
|
|
6
|
+
permission_mode: z.ZodOptional<z.ZodString>;
|
|
7
|
+
hook_event_name: z.ZodString;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
session_id: string;
|
|
10
|
+
transcript_path: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
hook_event_name: string;
|
|
13
|
+
permission_mode?: string | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
session_id: string;
|
|
16
|
+
transcript_path: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
hook_event_name: string;
|
|
19
|
+
permission_mode?: string | undefined;
|
|
20
|
+
}>;
|
|
21
|
+
declare const preToolUseInputSchema: z.ZodObject<{
|
|
22
|
+
session_id: z.ZodString;
|
|
23
|
+
transcript_path: z.ZodString;
|
|
24
|
+
cwd: z.ZodString;
|
|
25
|
+
permission_mode: z.ZodOptional<z.ZodString>;
|
|
26
|
+
hook_event_name: z.ZodString;
|
|
27
|
+
} & {
|
|
28
|
+
tool_name: z.ZodString;
|
|
29
|
+
tool_input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
30
|
+
tool_use_id: z.ZodString;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
session_id: string;
|
|
33
|
+
transcript_path: string;
|
|
34
|
+
cwd: string;
|
|
35
|
+
hook_event_name: string;
|
|
36
|
+
tool_name: string;
|
|
37
|
+
tool_input: Record<string, unknown>;
|
|
38
|
+
tool_use_id: string;
|
|
39
|
+
permission_mode?: string | undefined;
|
|
40
|
+
}, {
|
|
41
|
+
session_id: string;
|
|
42
|
+
transcript_path: string;
|
|
43
|
+
cwd: string;
|
|
44
|
+
hook_event_name: string;
|
|
45
|
+
tool_name: string;
|
|
46
|
+
tool_input: Record<string, unknown>;
|
|
47
|
+
tool_use_id: string;
|
|
48
|
+
permission_mode?: string | undefined;
|
|
49
|
+
}>;
|
|
50
|
+
declare const subagentStartInputSchema: z.ZodObject<{
|
|
51
|
+
session_id: z.ZodString;
|
|
52
|
+
transcript_path: z.ZodString;
|
|
53
|
+
cwd: z.ZodString;
|
|
54
|
+
permission_mode: z.ZodOptional<z.ZodString>;
|
|
55
|
+
hook_event_name: z.ZodString;
|
|
56
|
+
} & {
|
|
57
|
+
agent_id: z.ZodString;
|
|
58
|
+
agent_type: z.ZodString;
|
|
59
|
+
}, "strip", z.ZodTypeAny, {
|
|
60
|
+
session_id: string;
|
|
61
|
+
transcript_path: string;
|
|
62
|
+
cwd: string;
|
|
63
|
+
hook_event_name: string;
|
|
64
|
+
agent_id: string;
|
|
65
|
+
agent_type: string;
|
|
66
|
+
permission_mode?: string | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
session_id: string;
|
|
69
|
+
transcript_path: string;
|
|
70
|
+
cwd: string;
|
|
71
|
+
hook_event_name: string;
|
|
72
|
+
agent_id: string;
|
|
73
|
+
agent_type: string;
|
|
74
|
+
permission_mode?: string | undefined;
|
|
75
|
+
}>;
|
|
76
|
+
declare const teammateIdleInputSchema: z.ZodObject<{
|
|
77
|
+
session_id: z.ZodString;
|
|
78
|
+
transcript_path: z.ZodString;
|
|
79
|
+
cwd: z.ZodString;
|
|
80
|
+
permission_mode: z.ZodOptional<z.ZodString>;
|
|
81
|
+
hook_event_name: z.ZodString;
|
|
82
|
+
} & {
|
|
83
|
+
teammate_name: z.ZodOptional<z.ZodString>;
|
|
84
|
+
}, "strip", z.ZodTypeAny, {
|
|
85
|
+
session_id: string;
|
|
86
|
+
transcript_path: string;
|
|
87
|
+
cwd: string;
|
|
88
|
+
hook_event_name: string;
|
|
89
|
+
permission_mode?: string | undefined;
|
|
90
|
+
teammate_name?: string | undefined;
|
|
91
|
+
}, {
|
|
92
|
+
session_id: string;
|
|
93
|
+
transcript_path: string;
|
|
94
|
+
cwd: string;
|
|
95
|
+
hook_event_name: string;
|
|
96
|
+
permission_mode?: string | undefined;
|
|
97
|
+
teammate_name?: string | undefined;
|
|
98
|
+
}>;
|
|
99
|
+
/** @riviere-role external-client-model */
|
|
100
|
+
export type PreToolUseInput = z.infer<typeof preToolUseInputSchema>;
|
|
101
|
+
/** @riviere-role external-client-model */
|
|
102
|
+
export type SubagentStartInput = z.infer<typeof subagentStartInputSchema>;
|
|
103
|
+
/** @riviere-role external-client-model */
|
|
104
|
+
export type TeammateIdleInput = z.infer<typeof teammateIdleInputSchema>;
|
|
105
|
+
export { hookCommonInputSchema, preToolUseInputSchema, subagentStartInputSchema, teammateIdleInputSchema, };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const hookCommonInputSchema = z.object({
|
|
3
|
+
session_id: z.string(),
|
|
4
|
+
transcript_path: z.string(),
|
|
5
|
+
cwd: z.string(),
|
|
6
|
+
permission_mode: z.string().optional(),
|
|
7
|
+
hook_event_name: z.string(),
|
|
8
|
+
});
|
|
9
|
+
const preToolUseInputSchema = hookCommonInputSchema.extend({
|
|
10
|
+
tool_name: z.string(),
|
|
11
|
+
tool_input: z.record(z.unknown()),
|
|
12
|
+
tool_use_id: z.string(),
|
|
13
|
+
});
|
|
14
|
+
const subagentStartInputSchema = hookCommonInputSchema.extend({
|
|
15
|
+
agent_id: z.string(),
|
|
16
|
+
agent_type: z.string(),
|
|
17
|
+
});
|
|
18
|
+
const teammateIdleInputSchema = hookCommonInputSchema.extend({ teammate_name: z.string().optional(), });
|
|
19
|
+
export { hookCommonInputSchema, preToolUseInputSchema, subagentStartInputSchema, teammateIdleInputSchema, };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
const httpsRemotePattern = /github\.com\/([^/]+\/[^/.]+?)(?:\.git)?$/;
|
|
3
|
+
const sshRemotePattern = /github\.com:([^/]+\/[^/.]+?)(?:\.git)?$/;
|
|
4
|
+
/** @riviere-role external-client-service */
|
|
5
|
+
export function getRepositoryName(cwd) {
|
|
6
|
+
try {
|
|
7
|
+
const url = execFileSync('/usr/bin/git', ['remote', 'get-url', 'origin'], {
|
|
8
|
+
encoding: 'utf-8',
|
|
9
|
+
cwd,
|
|
10
|
+
}).trim();
|
|
11
|
+
const httpsMatch = httpsRemotePattern.exec(url);
|
|
12
|
+
if (httpsMatch?.[1] !== undefined)
|
|
13
|
+
return httpsMatch[1];
|
|
14
|
+
const sshMatch = sshRemotePattern.exec(url);
|
|
15
|
+
if (sshMatch?.[1] !== undefined)
|
|
16
|
+
return sshMatch[1];
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createStore } from '@nt-ai-lab/deterministic-agent-workflow-event-store';
|
|
3
|
+
/** @riviere-role external-client-service */
|
|
4
|
+
export function createDefaultProcessDeps() {
|
|
5
|
+
return {
|
|
6
|
+
getEnv: (name) => process.env[name],
|
|
7
|
+
exit: (code) => process.exit(code),
|
|
8
|
+
writeStdout: (value) => { process.stdout.write(value); },
|
|
9
|
+
writeStderr: (value) => { process.stderr.write(value); },
|
|
10
|
+
getArgv: () => process.argv,
|
|
11
|
+
readFile: (path) => readFileSync(path, 'utf8'),
|
|
12
|
+
appendToFile: (path, content) => appendFileSync(path, content),
|
|
13
|
+
buildStore: (dbPath) => createStore(dbPath),
|
|
14
|
+
};
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nt-ai-lab/deterministic-agent-workflow-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@nt-ai-lab/deterministic-agent-workflow-engine": "workspace:*",
|
|
15
|
+
"@nt-ai-lab/deterministic-agent-workflow-dsl": "workspace:*",
|
|
16
|
+
"@nt-ai-lab/deterministic-agent-workflow-event-store": "workspace:*",
|
|
17
|
+
"zod": "^3.25.76"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
}
|
|
22
|
+
}
|