@tiangong-ai/cli 0.0.36 → 0.0.37

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.
Files changed (31) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +38 -2
  3. package/dist/research/orchestration.js +36 -1
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition-routes.d.ts +14 -0
  6. package/dist/research/workspace/acquisition-routes.js +45 -0
  7. package/dist/research/workspace/acquisition-routes.js.map +1 -0
  8. package/dist/research/workspace/broker.js +54 -10
  9. package/dist/research/workspace/broker.js.map +1 -1
  10. package/dist/research/workspace/downloads.d.ts +6 -0
  11. package/dist/research/workspace/downloads.js +32 -8
  12. package/dist/research/workspace/downloads.js.map +1 -1
  13. package/dist/research/workspace/evidence-exhaustion.d.ts +45 -0
  14. package/dist/research/workspace/evidence-exhaustion.js +365 -0
  15. package/dist/research/workspace/evidence-exhaustion.js.map +1 -0
  16. package/dist/research/workspace/native-activity.d.ts +6 -0
  17. package/dist/research/workspace/native-activity.js +24 -7
  18. package/dist/research/workspace/native-activity.js.map +1 -1
  19. package/dist/research/workspace/preflight.js +18 -2
  20. package/dist/research/workspace/preflight.js.map +1 -1
  21. package/dist/research/workspace/projects.js +52 -0
  22. package/dist/research/workspace/projects.js.map +1 -1
  23. package/dist/research/workspace/runtime.d.ts +151 -3
  24. package/dist/research/workspace/runtime.js +176 -5
  25. package/dist/research/workspace/runtime.js.map +1 -1
  26. package/dist/research/workspace/scientific-design.d.ts +23 -0
  27. package/dist/research/workspace/scientific-design.js +176 -0
  28. package/dist/research/workspace/scientific-design.js.map +1 -1
  29. package/dist/research/workspace/setup-catalog.js +2 -2
  30. package/dist/research/workspace/types.d.ts +27 -0
  31. package/package.json +1 -1
@@ -9,6 +9,7 @@ import { stageEvidenceArtifacts } from "./artifacts.js";
9
9
  import { commitDiscoveryDecisions, materializeDiscoveryEvidence } from "./discovery.js";
10
10
  import { inspectDiscoveryProgress } from "./discovery-status.js";
11
11
  import { downloadBindingRecordSchema } from "./downloads.js";
12
+ import { parseEvidenceExhaustionHandoff, validateEvidenceExhaustionHandoff, } from "./evidence-exhaustion.js";
12
13
  import { loadProjectEvidenceReceipts, stageProjectEvidence } from "./evidence.js";
13
14
  import { appendEvidenceLedgerEvent, evidenceLedgerPath, listEvidenceCandidates, registerProjectInputCandidates, } from "./evidence-ledger.js";
14
15
  import { executeAgent } from "./executor.js";
@@ -24,13 +25,34 @@ import { configuredResearchSecrets, sanitizeResearchRecord, sanitizeResearchText
24
25
  import { parseEvidenceRecord, schemaForDiscoveryAssessmentBatch, parseStructuredStageOutput, schemaForStage, StructuredOutputError, } from "./schemas.js";
25
26
  import { canonicalJson, ensureDirectory, fileRecord, isObject, pathExists, readJsonFile, regularTreeFiles, resolveContained, sha256File, sha256Text, workspacePaths, writeJsonAtomic, writeTextAtomic, } from "./storage.js";
26
27
  import { loadWorkspaceConfig, verifyDoctorAttestation, withWorkspaceLock } from "./workspace.js";
28
+ function identifierSetSchema(minItems, maxItems) {
29
+ return {
30
+ type: "array",
31
+ minItems,
32
+ maxItems,
33
+ uniqueItems: true,
34
+ items: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
35
+ };
36
+ }
27
37
  export const researchHandoffRecordSchema = {
28
- $id: "https://schemas.tiangong.ai/research/handoff-request-v1.json",
38
+ $id: "https://schemas.tiangong.ai/research/handoff-request-v2.json",
29
39
  type: "object",
30
40
  additionalProperties: false,
31
- required: ["schemaVersion", "state", "reasonCode", "summary", "requestedActions", "evidenceGaps"],
41
+ required: [
42
+ "schemaVersion",
43
+ "kind",
44
+ "state",
45
+ "reasonCode",
46
+ "summary",
47
+ "requestedActions",
48
+ "evidenceGaps",
49
+ ],
32
50
  properties: {
33
- schemaVersion: { type: "integer", const: 1 },
51
+ schemaVersion: { type: "integer", const: 2 },
52
+ kind: {
53
+ type: "string",
54
+ enum: ["interactive-challenge", "external-wait", "evidence-exhausted"],
55
+ },
34
56
  state: {
35
57
  type: "string",
36
58
  enum: ["user-action-required", "external-response-required"],
@@ -51,7 +73,92 @@ export const researchHandoffRecordSchema = {
51
73
  uniqueItems: true,
52
74
  items: { type: "string", minLength: 1, maxLength: 500 },
53
75
  },
76
+ exhaustion: {
77
+ type: "object",
78
+ additionalProperties: false,
79
+ required: ["missingEvidenceRoleIds", "routeAttempts", "remainingRouteIds"],
80
+ properties: {
81
+ missingEvidenceRoleIds: identifierSetSchema(1, 100),
82
+ routeAttempts: {
83
+ type: "array",
84
+ minItems: 1,
85
+ maxItems: 100,
86
+ items: {
87
+ type: "object",
88
+ additionalProperties: false,
89
+ required: ["routeId", "terminalEventHashes", "outcome"],
90
+ properties: {
91
+ routeId: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
92
+ terminalEventHashes: {
93
+ type: "array",
94
+ minItems: 1,
95
+ maxItems: 100,
96
+ uniqueItems: true,
97
+ items: { type: "string", pattern: "^[a-f0-9]{64}$" },
98
+ },
99
+ outcome: {
100
+ type: "string",
101
+ enum: ["completed-insufficient", "access-blocked", "deterministic-unavailable"],
102
+ },
103
+ },
104
+ },
105
+ },
106
+ remainingRouteIds: identifierSetSchema(0, 100),
107
+ },
108
+ },
109
+ accessRequests: {
110
+ type: "array",
111
+ minItems: 0,
112
+ maxItems: 100,
113
+ items: {
114
+ type: "object",
115
+ additionalProperties: false,
116
+ required: [
117
+ "id",
118
+ "routeId",
119
+ "resourceType",
120
+ "resourceName",
121
+ "officialLocator",
122
+ "evidenceRoleIds",
123
+ "rationale",
124
+ "alternativesTriedRouteIds",
125
+ "requestedAction",
126
+ "resumeCriteria",
127
+ "costStatus",
128
+ ],
129
+ properties: {
130
+ id: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
131
+ routeId: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
132
+ resourceType: {
133
+ type: "string",
134
+ enum: [
135
+ "database-subscription",
136
+ "article-purchase",
137
+ "institutional-access",
138
+ "licensed-dataset",
139
+ "owner-provided-material",
140
+ "external-data-request",
141
+ "field-data-collection",
142
+ ],
143
+ },
144
+ resourceName: { type: "string", minLength: 3, maxLength: 500 },
145
+ officialLocator: { type: ["string", "null"], format: "uri" },
146
+ evidenceRoleIds: identifierSetSchema(1, 100),
147
+ rationale: { type: "string", minLength: 8, maxLength: 2_000 },
148
+ alternativesTriedRouteIds: identifierSetSchema(1, 100),
149
+ requestedAction: { type: "string", minLength: 8, maxLength: 1_000 },
150
+ resumeCriteria: { type: "string", minLength: 8, maxLength: 1_000 },
151
+ costStatus: { type: "string", enum: ["unknown", "provider-quote-required"] },
152
+ },
153
+ },
154
+ },
54
155
  },
156
+ allOf: [
157
+ {
158
+ if: { properties: { kind: { const: "evidence-exhausted" } } },
159
+ then: { required: ["exhaustion", "accessRequests"] },
160
+ },
161
+ ],
55
162
  };
56
163
  export async function runResearchWorkspace(root, options, packageExecutor = executeAgent) {
57
164
  return runResearchWorkspaceInternal(root, options, packageExecutor, false);
@@ -299,6 +406,21 @@ export async function prepareNativeResearchStage(input) {
299
406
  maxWallSeconds: config.budget.packageMaxWallSeconds[input.stage],
300
407
  },
301
408
  commands: {
409
+ inspectAccess: project.scientificDesign
410
+ ? {
411
+ argv: [
412
+ "tiangong-ai",
413
+ "research",
414
+ "project",
415
+ "access",
416
+ "status",
417
+ project.id,
418
+ "--workspace",
419
+ input.root,
420
+ "--json",
421
+ ],
422
+ }
423
+ : null,
302
424
  requestHandoff: {
303
425
  argv: [
304
426
  "tiangong-ai",
@@ -519,6 +641,7 @@ export async function prepareNativeResearchStage(input) {
519
641
  rules: [
520
642
  "Current native host performs producer reasoning; the CLI does not spawn a producer.",
521
643
  "Native Web/Browser activity is visible in the evidence ledger but becomes admissible only after broker/input formalization and strict assessment.",
644
+ "An interactive challenge pauses immediately. A material evidence-exhausted handoff is valid only after inspectAccess proves every required plan-bound agent route with exact terminal event hashes.",
522
645
  "When the next material step requires user authorization or an external response, request a durable handoff and stop; do not keep searching low-yield substitutes.",
523
646
  "Only broker receipts or registered immutable inputs may support discover output.",
524
647
  "Only exact, structurally validated, content-addressed artifacts may support full-text acquisition claims.",
@@ -795,6 +918,17 @@ export async function requestResearchHandoff(input) {
795
918
  details: { state: project.handoff.state },
796
919
  });
797
920
  }
921
+ const evidenceHandoff = value.kind === "evidence-exhausted"
922
+ ? await validateEvidenceExhaustionHandoff({
923
+ root: input.root,
924
+ project,
925
+ state: value.state,
926
+ value: {
927
+ exhaustion: value.exhaustion,
928
+ accessRequests: value.accessRequests,
929
+ },
930
+ })
931
+ : null;
798
932
  const activePath = nativeStageSessionPath(input.root, input.projectId);
799
933
  const session = (await pathExists(activePath))
800
934
  ? await readNativeStageSession(input.root, input.projectId)
@@ -818,10 +952,13 @@ export async function requestResearchHandoff(input) {
818
952
  const requestedAt = new Date().toISOString();
819
953
  project.handoff = {
820
954
  state: value.state,
955
+ kind: value.kind,
821
956
  reasonCode: value.reasonCode,
822
957
  summary: value.summary,
823
958
  requestedActions: value.requestedActions,
824
959
  evidenceGaps: value.evidenceGaps,
960
+ exhaustion: evidenceHandoff?.exhaustion ?? null,
961
+ accessRequests: evidenceHandoff?.accessRequests ?? [],
825
962
  requestedAt,
826
963
  resolvedAt: null,
827
964
  resolutionNote: null,
@@ -830,10 +967,13 @@ export async function requestResearchHandoff(input) {
830
967
  await saveProject(input.root, project);
831
968
  const eventPayload = {
832
969
  state: value.state,
970
+ kind: value.kind,
833
971
  reasonCode: value.reasonCode,
834
972
  summary: value.summary,
835
973
  requestedActions: value.requestedActions,
836
974
  evidenceGaps: value.evidenceGaps,
975
+ exhaustion: evidenceHandoff?.exhaustion ?? null,
976
+ accessRequests: evidenceHandoff?.accessRequests ?? [],
837
977
  requestedAt,
838
978
  interruptedSessionId: session?.packet.sessionId ?? null,
839
979
  interruptedPackageId: session?.packet.packageId ?? null,
@@ -894,18 +1034,22 @@ function parseHandoffRequest(value) {
894
1034
  const sanitized = sanitizeResearchRecord(value, configuredResearchSecrets(process.env));
895
1035
  const allowed = new Set([
896
1036
  "schemaVersion",
1037
+ "kind",
897
1038
  "state",
898
1039
  "reasonCode",
899
1040
  "summary",
900
1041
  "requestedActions",
901
1042
  "evidenceGaps",
1043
+ "exhaustion",
1044
+ "accessRequests",
902
1045
  ]);
903
1046
  const requestedActions = Array.isArray(sanitized.requestedActions)
904
1047
  ? sanitized.requestedActions
905
1048
  : [];
906
1049
  const evidenceGaps = Array.isArray(sanitized.evidenceGaps) ? sanitized.evidenceGaps : [];
907
1050
  if (Object.keys(sanitized).some((key) => !allowed.has(key)) ||
908
- sanitized.schemaVersion !== 1 ||
1051
+ sanitized.schemaVersion !== 2 ||
1052
+ !["interactive-challenge", "external-wait", "evidence-exhausted"].includes(String(sanitized.kind)) ||
909
1053
  !["user-action-required", "external-response-required"].includes(String(sanitized.state)) ||
910
1054
  typeof sanitized.reasonCode !== "string" ||
911
1055
  !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(sanitized.reasonCode) ||
@@ -925,12 +1069,39 @@ function parseHandoffRequest(value) {
925
1069
  exitCode: 2,
926
1070
  });
927
1071
  }
1072
+ const kind = sanitized.kind;
1073
+ const state = sanitized.state;
1074
+ if ((kind === "interactive-challenge" && state !== "user-action-required") ||
1075
+ (kind === "external-wait" && state !== "external-response-required")) {
1076
+ throw new CliError("Handoff request failed validation.", {
1077
+ code: "RESEARCH_PROJECT_HANDOFF_INVALID",
1078
+ exitCode: 2,
1079
+ });
1080
+ }
1081
+ let exhaustion = null;
1082
+ let accessRequests = [];
1083
+ if (kind === "evidence-exhausted") {
1084
+ const parsed = parseEvidenceExhaustionHandoff(sanitized.exhaustion, sanitized.accessRequests);
1085
+ exhaustion = parsed.exhaustion;
1086
+ accessRequests = parsed.accessRequests;
1087
+ }
1088
+ else if (sanitized.exhaustion !== undefined ||
1089
+ (sanitized.accessRequests !== undefined &&
1090
+ (!Array.isArray(sanitized.accessRequests) || sanitized.accessRequests.length > 0))) {
1091
+ throw new CliError("Handoff request failed validation.", {
1092
+ code: "RESEARCH_PROJECT_HANDOFF_INVALID",
1093
+ exitCode: 2,
1094
+ });
1095
+ }
928
1096
  return {
929
- state: sanitized.state,
1097
+ kind,
1098
+ state,
930
1099
  reasonCode: sanitized.reasonCode,
931
1100
  summary: sanitized.summary.trim(),
932
1101
  requestedActions: requestedActions,
933
1102
  evidenceGaps: evidenceGaps,
1103
+ exhaustion,
1104
+ accessRequests,
934
1105
  };
935
1106
  }
936
1107
  async function executeWorkPackage(root, projectId, packageId, config, options, requestId, packageExecutor, doctorAttestation) {