huskies: merge 1088
This commit is contained in:
@@ -241,6 +241,8 @@ export interface WorkItemContent {
|
|||||||
stage: string;
|
stage: string;
|
||||||
name: string;
|
name: string;
|
||||||
agent: string | null;
|
agent: string | null;
|
||||||
|
/** Origin JSON string (story 1088), or null for pre-origin items. */
|
||||||
|
origin: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Result for a single test case from the server's test runner. */
|
/** Result for a single test case from the server's test runner. */
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const DEFAULT_CONTENT = {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "Big Title Story",
|
name: "Big Title Story",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const DEFAULT_CONTENT = {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "Big Title Story",
|
name: "Big Title Story",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sampleTestResults: TestResultsResponse = {
|
const sampleTestResults: TestResultsResponse = {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const DEFAULT_CONTENT = {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "Big Title Story",
|
name: "Big Title Story",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -127,6 +128,7 @@ describe("WorkItemDetailPanel", () => {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "My Story Name",
|
name: "My Story Name",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
<WorkItemDetailPanel
|
<WorkItemDetailPanel
|
||||||
@@ -146,6 +148,7 @@ describe("WorkItemDetailPanel", () => {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "My Story Name",
|
name: "My Story Name",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
<WorkItemDetailPanel
|
<WorkItemDetailPanel
|
||||||
@@ -164,6 +167,7 @@ describe("WorkItemDetailPanel", () => {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "My Story Name",
|
name: "My Story Name",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
<WorkItemDetailPanel
|
<WorkItemDetailPanel
|
||||||
@@ -186,6 +190,7 @@ describe("WorkItemDetailPanel", () => {
|
|||||||
stage: "current",
|
stage: "current",
|
||||||
name: "My Story Name",
|
name: "My Story Name",
|
||||||
agent: null,
|
agent: null,
|
||||||
|
origin: null,
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
<WorkItemDetailPanel
|
<WorkItemDetailPanel
|
||||||
|
|||||||
@@ -20,6 +20,26 @@ import { stripDisplayContent } from "./workItemDetailPanelUtils";
|
|||||||
|
|
||||||
const { useCallback, useEffect, useRef, useState } = React;
|
const { useCallback, useEffect, useRef, useState } = React;
|
||||||
|
|
||||||
|
/** Parse and format an origin JSON string for display. */
|
||||||
|
function formatOrigin(origin: string | null): string {
|
||||||
|
if (!origin) return "unknown";
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(origin) as {
|
||||||
|
kind?: string;
|
||||||
|
id?: string;
|
||||||
|
ts?: number;
|
||||||
|
};
|
||||||
|
const kind = obj.kind ?? "unknown";
|
||||||
|
const id = obj.id ? ` (${obj.id})` : "";
|
||||||
|
const ts = obj.ts
|
||||||
|
? ` at ${new Date(obj.ts * 1000).toISOString().replace("T", " ").slice(0, 19)}Z`
|
||||||
|
: "";
|
||||||
|
return `${kind}${id}${ts}`;
|
||||||
|
} catch {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface WorkItemDetailPanelProps {
|
interface WorkItemDetailPanelProps {
|
||||||
storyId: string;
|
storyId: string;
|
||||||
pipelineVersion: number;
|
pipelineVersion: number;
|
||||||
@@ -38,6 +58,7 @@ export function WorkItemDetailPanel({
|
|||||||
const [stage, setStage] = useState<string>("");
|
const [stage, setStage] = useState<string>("");
|
||||||
const [name, setName] = useState<string | null>(null);
|
const [name, setName] = useState<string | null>(null);
|
||||||
const [assignedAgent, setAssignedAgent] = useState<string | null>(null);
|
const [assignedAgent, setAssignedAgent] = useState<string | null>(null);
|
||||||
|
const [origin, setOrigin] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [agentInfo, setAgentInfo] = useState<AgentInfo | null>(null);
|
const [agentInfo, setAgentInfo] = useState<AgentInfo | null>(null);
|
||||||
@@ -63,6 +84,7 @@ export function WorkItemDetailPanel({
|
|||||||
setStage(data.stage);
|
setStage(data.stage);
|
||||||
setName(data.name);
|
setName(data.name);
|
||||||
setAssignedAgent(data.agent);
|
setAssignedAgent(data.agent);
|
||||||
|
setOrigin(data.origin);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load content");
|
setError(err instanceof Error ? err.message : "Failed to load content");
|
||||||
@@ -289,6 +311,19 @@ export function WorkItemDetailPanel({
|
|||||||
|
|
||||||
<TestResultsSection testResults={testResults} />
|
<TestResultsSection testResults={testResults} />
|
||||||
|
|
||||||
|
{!loading && (
|
||||||
|
<div
|
||||||
|
data-testid="detail-panel-origin"
|
||||||
|
style={{
|
||||||
|
fontSize: "0.75em",
|
||||||
|
color: "#555",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
origin: {formatOrigin(origin)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ pub use write::{
|
|||||||
bump_retry_count, migrate_legacy_stage_strings, migrate_merge_job, migrate_names_from_slugs,
|
bump_retry_count, migrate_legacy_stage_strings, migrate_merge_job, migrate_names_from_slugs,
|
||||||
migrate_node_claims_to_agent_claims, migrate_story_ids_to_numeric, name_from_story_id,
|
migrate_node_claims_to_agent_claims, migrate_story_ids_to_numeric, name_from_story_id,
|
||||||
purge_done_stage_merge_jobs, set_agent, set_depends_on, set_epic, set_item_type, set_name,
|
purge_done_stage_merge_jobs, set_agent, set_depends_on, set_epic, set_item_type, set_name,
|
||||||
set_plan_state, set_qa_mode, set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
set_origin, set_plan_state, set_qa_mode, set_resume_to, set_resume_to_raw, set_retry_count,
|
||||||
|
write_item,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ pub struct CrdtItemDump {
|
|||||||
/// Hex-encoded OpId of the list insert op — cross-reference with `crdt_ops`.
|
/// Hex-encoded OpId of the list insert op — cross-reference with `crdt_ops`.
|
||||||
pub content_index: String,
|
pub content_index: String,
|
||||||
pub is_deleted: bool,
|
pub is_deleted: bool,
|
||||||
|
/// Origin JSON string, or `None` for items that pre-date story 1088.
|
||||||
|
pub origin: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Top-level debug dump of the in-memory CRDT state.
|
/// Top-level debug dump of the in-memory CRDT state.
|
||||||
@@ -149,6 +151,10 @@ pub fn dump_crdt_state(story_id_filter: Option<&str>) -> CrdtStateDump {
|
|||||||
JsonValue::Number(n) if n > 0.0 => Some(n),
|
JsonValue::Number(n) if n > 0.0 => Some(n),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
let origin = match item_crdt.origin.view() {
|
||||||
|
JsonValue::String(s) if !s.is_empty() => Some(s),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
let content_index = op.id.iter().map(|b| format!("{b:02x}")).collect::<String>();
|
let content_index = op.id.iter().map(|b| format!("{b:02x}")).collect::<String>();
|
||||||
|
|
||||||
@@ -163,6 +169,7 @@ pub fn dump_crdt_state(story_id_filter: Option<&str>) -> CrdtStateDump {
|
|||||||
claim_ts,
|
claim_ts,
|
||||||
content_index,
|
content_index,
|
||||||
is_deleted: op.is_deleted,
|
is_deleted: op.is_deleted,
|
||||||
|
origin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,6 +415,11 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let origin = match item.origin.view() {
|
||||||
|
JsonValue::String(s) if !s.is_empty() => Some(s),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
let stage = project_stage_for_view(
|
let stage = project_stage_for_view(
|
||||||
&stage_str,
|
&stage_str,
|
||||||
&story_id,
|
&story_id,
|
||||||
@@ -429,6 +441,7 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
|
|||||||
qa_mode,
|
qa_mode,
|
||||||
item_type,
|
item_type,
|
||||||
epic,
|
epic,
|
||||||
|
origin,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ pub struct PipelineItemCrdt {
|
|||||||
/// means no merge task is in flight. Projected into `Stage::Merge {
|
/// means no merge task is in flight. Projected into `Stage::Merge {
|
||||||
/// server_start_time }` so callers never read this register directly.
|
/// server_start_time }` so callers never read this register directly.
|
||||||
pub merge_server_start: LwwRegisterCrdt<f64>,
|
pub merge_server_start: LwwRegisterCrdt<f64>,
|
||||||
|
/// Story 1088: origin of the work item — who or what created it.
|
||||||
|
///
|
||||||
|
/// Stored as a compact JSON string, e.g.
|
||||||
|
/// `{"kind":"user","id":"","ts":1716768000.0}` or
|
||||||
|
/// `{"kind":"agent","id":"coder-1","ts":1716768000.0}`.
|
||||||
|
/// Empty string on older items that pre-date this register; the typed
|
||||||
|
/// read path surfaces those as `None`, which the UI renders as `"unknown"`.
|
||||||
|
pub origin: LwwRegisterCrdt<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CRDT node that holds a single peer's presence entry.
|
/// CRDT node that holds a single peer's presence entry.
|
||||||
@@ -203,6 +211,9 @@ pub struct WorkItem {
|
|||||||
pub(super) item_type: Option<crate::io::story_metadata::ItemType>,
|
pub(super) item_type: Option<crate::io::story_metadata::ItemType>,
|
||||||
/// Epic this item belongs to. `None` when the item has no parent epic.
|
/// Epic this item belongs to. `None` when the item has no parent epic.
|
||||||
pub(super) epic: Option<EpicId>,
|
pub(super) epic: Option<EpicId>,
|
||||||
|
/// Origin of the work item (story 1088). `None` for items created before
|
||||||
|
/// the origin register was introduced; those display as `"unknown"`.
|
||||||
|
pub(super) origin: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkItem {
|
impl WorkItem {
|
||||||
@@ -261,6 +272,12 @@ impl WorkItem {
|
|||||||
self.epic
|
self.epic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Origin of the work item (story 1088), or `None` for items created before
|
||||||
|
/// the origin register was introduced.
|
||||||
|
pub fn origin(&self) -> Option<&str> {
|
||||||
|
self.origin.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a `WorkItem` for use in tests outside `crdt_state::*`.
|
/// Construct a `WorkItem` for use in tests outside `crdt_state::*`.
|
||||||
///
|
///
|
||||||
/// Within `crdt_state` use a struct literal directly (fields are `pub(super)`).
|
/// Within `crdt_state` use a struct literal directly (fields are `pub(super)`).
|
||||||
@@ -286,6 +303,7 @@ impl WorkItem {
|
|||||||
qa_mode,
|
qa_mode,
|
||||||
item_type,
|
item_type,
|
||||||
epic,
|
epic,
|
||||||
|
origin: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,31 @@ pub fn set_plan_state(story_id: &str, state: crate::pipeline_state::PlanState) -
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the `origin` CRDT register for a pipeline item (story 1088).
|
||||||
|
///
|
||||||
|
/// Writes a compact JSON string describing who or what created the item, e.g.
|
||||||
|
/// `{"kind":"user","id":"","ts":1716768000.0}` or
|
||||||
|
/// `{"kind":"agent","id":"coder-1","ts":1716768000.0}`.
|
||||||
|
///
|
||||||
|
/// Passing an empty string is treated as "no origin set" (equivalent to the
|
||||||
|
/// pre-1088 state for older items). Returns `true` if the item was found and
|
||||||
|
/// the op was applied, `false` otherwise.
|
||||||
|
pub fn set_origin(story_id: &str, origin: &str) -> bool {
|
||||||
|
let Some(state_mutex) = get_crdt() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Ok(mut state) = state_mutex.lock() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(&idx) = state.index.get(story_id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
apply_and_persist(&mut state, |s| {
|
||||||
|
s.crdt.doc.items[idx].origin.set(origin.to_string())
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Write a pipeline item state through CRDT operations.
|
/// Write a pipeline item state through CRDT operations.
|
||||||
///
|
///
|
||||||
/// If the item exists, updates its registers. If not, inserts a new item
|
/// If the item exists, updates its registers. If not, inserts a new item
|
||||||
@@ -394,6 +419,7 @@ pub fn write_item(
|
|||||||
"resume_to": "",
|
"resume_to": "",
|
||||||
"plan_state": "",
|
"plan_state": "",
|
||||||
"merge_server_start": merge_server_start_val,
|
"merge_server_start": merge_server_start_val,
|
||||||
|
"origin": "",
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@@ -424,6 +450,7 @@ pub fn write_item(
|
|||||||
item.resume_to.advance_seq(floor);
|
item.resume_to.advance_seq(floor);
|
||||||
item.plan_state.advance_seq(floor);
|
item.plan_state.advance_seq(floor);
|
||||||
item.merge_server_start.advance_seq(floor);
|
item.merge_server_start.advance_seq(floor);
|
||||||
|
item.origin.advance_seq(floor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast a CrdtEvent for the new item.
|
// Broadcast a CrdtEvent for the new item.
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ mod migrations;
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub use item::{
|
pub use item::{
|
||||||
bump_retry_count, set_agent, set_depends_on, set_epic, set_item_type, set_name, set_plan_state,
|
bump_retry_count, set_agent, set_depends_on, set_epic, set_item_type, set_name, set_origin,
|
||||||
set_qa_mode, set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
set_plan_state, set_qa_mode, set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -434,6 +434,7 @@ async fn handle_work_items_get(params: Value) -> Value {
|
|||||||
"stage": c.stage,
|
"stage": c.stage,
|
||||||
"name": c.name,
|
"name": c.name,
|
||||||
"agent": c.agent,
|
"agent": c.agent,
|
||||||
|
"origin": c.origin,
|
||||||
}),
|
}),
|
||||||
Err(e) => serde_json::json!({"error": e.to_string()}),
|
Err(e) => serde_json::json!({"error": e.to_string()}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ pub(crate) fn tool_dump_crdt(args: &Value) -> Result<String, String> {
|
|||||||
"claimed_at": item.claim_ts,
|
"claimed_at": item.claim_ts,
|
||||||
"content_index": item.content_index,
|
"content_index": item.content_index,
|
||||||
"is_deleted": item.is_deleted,
|
"is_deleted": item.is_deleted,
|
||||||
|
"origin": item.origin,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ pub(super) async fn tool_status(args: &Value, ctx: &AppContext) -> Result<String
|
|||||||
if !deps.is_empty() {
|
if !deps.is_empty() {
|
||||||
front_matter.insert("depends_on".to_string(), json!(deps));
|
front_matter.insert("depends_on".to_string(), json!(deps));
|
||||||
}
|
}
|
||||||
|
// Story 1088: origin tracking.
|
||||||
|
let origin_str = view.origin().unwrap_or("unknown");
|
||||||
|
front_matter.insert("origin".to_string(), json!(origin_str));
|
||||||
let stage_claim = match &typed_item.stage {
|
let stage_claim = match &typed_item.stage {
|
||||||
crate::pipeline_state::Stage::Coding { claim, .. } => claim.as_ref(),
|
crate::pipeline_state::Stage::Coding { claim, .. } => claim.as_ref(),
|
||||||
crate::pipeline_state::Stage::Merge { claim, .. } => claim.as_ref(),
|
crate::pipeline_state::Stage::Merge { claim, .. } => claim.as_ref(),
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ pub(crate) fn tool_create_bug(args: &Value, ctx: &AppContext) -> Result<String,
|
|||||||
depends_on.as_deref(),
|
depends_on.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
crate::crdt_state::set_origin(&bug_id, &super::build_origin(args));
|
||||||
|
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.watcher_tx
|
.watcher_tx
|
||||||
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ pub(crate) fn tool_create_epic(args: &Value, ctx: &AppContext) -> Result<String,
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
crate::crdt_state::set_origin(&epic_id, &super::build_origin(args));
|
||||||
|
|
||||||
Ok(format!("Created epic: {epic_id}"))
|
Ok(format!("Created epic: {epic_id}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,33 @@ mod refactor;
|
|||||||
mod spike;
|
mod spike;
|
||||||
mod story;
|
mod story;
|
||||||
|
|
||||||
|
/// Build a compact origin JSON string for a newly-created work item (story 1088).
|
||||||
|
///
|
||||||
|
/// `args` may contain an `"origin"` object with `kind`, `id`, and `ts` fields
|
||||||
|
/// supplied by the caller (e.g. a coder agent passing its own identity). When
|
||||||
|
/// absent the default is `{"kind":"user","id":"","ts":<now>}`.
|
||||||
|
///
|
||||||
|
/// Callers that create items on behalf of system automation (e.g. gate-failure
|
||||||
|
/// auto-filing) should pass `kind = "system"` and `id = "<automation-name>"`.
|
||||||
|
pub(super) fn build_origin(args: &serde_json::Value) -> String {
|
||||||
|
let ts = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs_f64();
|
||||||
|
|
||||||
|
if let Some(origin_obj) = args.get("origin").and_then(|v| v.as_object()) {
|
||||||
|
let kind = origin_obj
|
||||||
|
.get("kind")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("user");
|
||||||
|
let id = origin_obj.get("id").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let ts_val = origin_obj.get("ts").and_then(|v| v.as_f64()).unwrap_or(ts);
|
||||||
|
serde_json::json!({"kind": kind, "id": id, "ts": ts_val}).to_string()
|
||||||
|
} else {
|
||||||
|
serde_json::json!({"kind": "user", "id": "", "ts": ts}).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) use bug::{tool_close_bug, tool_create_bug, tool_list_bugs};
|
pub(crate) use bug::{tool_close_bug, tool_create_bug, tool_list_bugs};
|
||||||
pub(crate) use criteria::{
|
pub(crate) use criteria::{
|
||||||
tool_add_criterion, tool_check_criterion, tool_edit_criterion, tool_ensure_acceptance,
|
tool_add_criterion, tool_check_criterion, tool_edit_criterion, tool_ensure_acceptance,
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ pub(crate) fn tool_create_refactor(args: &Value, ctx: &AppContext) -> Result<Str
|
|||||||
depends_on.as_deref(),
|
depends_on.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
crate::crdt_state::set_origin(&refactor_id, &super::build_origin(args));
|
||||||
|
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.watcher_tx
|
.watcher_tx
|
||||||
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ pub(crate) fn tool_create_spike(args: &Value, ctx: &AppContext) -> Result<String
|
|||||||
depends_on.as_deref(),
|
depends_on.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
crate::crdt_state::set_origin(&spike_id, &super::build_origin(args));
|
||||||
|
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.watcher_tx
|
.watcher_tx
|
||||||
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ pub(crate) fn tool_create_story(args: &Value, ctx: &AppContext) -> Result<String
|
|||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
crate::crdt_state::set_origin(&story_id, &super::super::build_origin(args));
|
||||||
|
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.watcher_tx
|
.watcher_tx
|
||||||
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
.send(crate::io::watcher::WatcherEvent::NewItemCreated {
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ pub struct WorkItemContent {
|
|||||||
pub stage: crate::pipeline_state::Stage,
|
pub stage: crate::pipeline_state::Stage,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub agent: Option<crate::config::AgentName>,
|
pub agent: Option<crate::config::AgentName>,
|
||||||
|
/// Origin of the work item (story 1088). `None` for items that pre-date
|
||||||
|
/// the origin register; the web UI renders these as `"unknown"`.
|
||||||
|
pub origin: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single entry in the project's configured agent roster.
|
/// A single entry in the project's configured agent roster.
|
||||||
@@ -176,6 +179,9 @@ pub fn get_work_item_content(
|
|||||||
.map(|v| v.name().to_string())
|
.map(|v| v.name().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let crdt_agent = crdt_view.as_ref().and_then(|v| v.agent());
|
let crdt_agent = crdt_view.as_ref().and_then(|v| v.agent());
|
||||||
|
let crdt_origin = crdt_view
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.origin().map(str::to_string));
|
||||||
|
|
||||||
for (stage_dir, stage) in &stages {
|
for (stage_dir, stage) in &stages {
|
||||||
if let Some(content) = io::read_work_item_from_stage(&work_dir, stage_dir, &filename)? {
|
if let Some(content) = io::read_work_item_from_stage(&work_dir, stage_dir, &filename)? {
|
||||||
@@ -184,6 +190,7 @@ pub fn get_work_item_content(
|
|||||||
stage: stage.clone(),
|
stage: stage.clone(),
|
||||||
name: crdt_name.clone(),
|
name: crdt_name.clone(),
|
||||||
agent: crdt_agent,
|
agent: crdt_agent,
|
||||||
|
origin: crdt_origin.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,6 +208,7 @@ pub fn get_work_item_content(
|
|||||||
stage,
|
stage,
|
||||||
name: crdt_name,
|
name: crdt_name,
|
||||||
agent: crdt_agent,
|
agent: crdt_agent,
|
||||||
|
origin: crdt_origin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user